博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
计算机科学中抽象的好处与问题—伪共享实例分析
阅读量:6876 次
发布时间:2019-06-26

本文共 3372 字,大约阅读时间需要 11 分钟。

有一句名言**“计算机科学中的任何问题都可以通过加上一层间接层来解决”**,一层不够就再加一层。后半句是我加的 (* ̄︶ ̄) ,虽然有点玩笑的意思,但是也的确能说明一些问题。计算机科学的确是靠着一层又一层的抽象与封装解决了巨量的问题。

我们来简单回顾一下: 开始的时候是程序员直接输入二进制指令来操纵硬件的,不仅性能低下还很耗费用户时间; 于是后来出现了操作系统,用文件、进程与线程、地址空间抽象了磁盘、CPU与内存,统一和简化了硬件访问方式; 机器语言对用户不友好,于是便出现了汇编语言、中级语言(如C)、高级语言(如Java)的包装,其最终执行还是要转化为机器语言; 裸高级语言大家还用得不爽,觉得开发效率低,于是又出现了各种框架(如Spring、Hibernate) ......

这样一层一层抽象包装下来,我们要想实现一个功能比如定时写文件等已经变成了很简单的事,只需要几行代码就搞定了。 但是抽象层数过多就会导致我们顶层的用户有时候会出现一些莫名其妙的问题,我们用一个实际的案例伪共享来说明一下

public class FalseSharing {    private static AtomicLong time = new AtomicLong(0);    public static void main(String... args) throws InterruptedException {        int testNum = 50;        for (int i = 0 ; i< testNum;i++){// 测试50次            Thread thread = new Thread(new Job());            thread.start();            thread.join();        }        System.out.println(time.get()/1000/testNum + " us,avg");    }    static class Job implements Runnable{        @Override        public void run() {            int number = 8;            int iterationNumber = 20000;            CountDownLatch countDownLatch = new CountDownLatch(number);            Obj[] objArray = new Obj[number];            for (int i = 0;i < number;i++) {                objArray[i] = new Obj();            }            long start = System.nanoTime();            for (int i = 0;i < number;i++){                int ii = i;                Thread thread = new Thread(new Runnable() {                    int iterationNumberInner = iterationNumber;                    @Override                    public void run() {                        while (iterationNumberInner-->0){                            objArray[ii].aLong+=1L;                        }                        countDownLatch.countDown();                    }                });                thread.start();            }            try {                countDownLatch.await();            } catch (InterruptedException e) {                e.printStackTrace();            }            long end = System.nanoTime();            time.getAndAdd(end-start);        }    }    @Contended    private static final class Obj{        private volatile long aLong = 8L;//8Bytes//        private volatile long a=2L,b=2L,c=2L,d=2L,e=2L,f=2L,g=2L;//*****    }}复制代码

全部代码,为了避免Java JIT(这也是一层抽象)的影响,我们每次执行都要加参数-Xint来强制使用。 在我的机器上(4core,8processor,Core-i7),直接运行这段代码得到结果14594 us,avg这个级别,在结果1基础上把//*****一行取消注释得到结果23916 us,avg 这个级别,在结果1基础上运行参数加上-XX:-RestrictContended使得@Contended起作用就能得到结果33466 us,avg

这时候顶层用户就会莫名奇妙了,怎么多了几个字段运行时间反而减小了?怎么加上@Contended后时间就更短了? 从Java代码这一层次的抽象来看,完全是没有问题的,那么问题究竟在哪呢?

我们知道一个CPU中的每个核是有自己的Cache的,高级别的L1是自己私有的,更低级别的L2、L3等可能是私有的,也可能是不同核共享的。这些不同级别的缓存(一次访问时间在几个ns左右)是用来弥补CPU的快速(一个周期通常零点几个ns)和内存访问的慢速(一次访问时间在几十个ns)之间的鸿沟的,而且是以CacheLine Size: N Bytes(Core-i7是64)为基本单位的,依据局部性原理一次性把内存中该访问变量周围的N Bytes内容拷贝到Cache中,如果一个对象不够N Bytes,就有可能和几个对象共用一个CacheLine,这样一个线程刷新Cacheline就会导致其他线程的缓存失效,要去更低级别的Cache甚至内存访问,就大大降低了访问速度。

这样回到刚才的问题,多加几个字段能在一定程度上增大该对象所占空间,减小共用CacheLine的几率,所以访问时间减少了,而@Contended则使得一个对象一个CacheLine,直接帮我们避免了伪共享,所以访问时间更少了。要解决这个问题,光知道Java这一层抽象(语法、JDK API等)是不可能的,还得懂操作系统、甚至CPU芯片原理这些层抽象才行。

再比如说,JVM帮我们把C/C++的手动内存管理封装了一层抽象做到内存自动管理从而解放了我们,我们当然用得很爽,但是如果我们不懂这一层的抽象与封装,那么程序OOM的时候就只能傻眼了。

最后总结一下,计算机科学中的任何问题都可以通过加上一层间接层来解决,这是很正确的,但是也正是因为一层一层的抽象和包装,导致出了问题后很难定位,你都不知道问题究竟是出现在哪一层。所以要想提高技术水平不仅要知其然(看得见最顶层的包装)也要知其所以然(看得见底层的包装),每一层如果都懂或者说了解一些,那么出了问题很大程度上都可以凭直觉定位,即使不能凭直觉也可以通过各种手段debug,只会最顶层的抽象很多时候就只能望bug兴叹了。

访问,来自。

转载地址:http://mfgfl.baihongyu.com/

你可能感兴趣的文章
Cong!
查看>>
PHP语言拓展json模块
查看>>
spring 配置文件applicationContext.xml命名空间及标签解析
查看>>
我的友情链接
查看>>
回到顶部代码(兼容IE6)
查看>>
web.xml文件的作用
查看>>
iOS开发篇——OC延展类目协议介绍
查看>>
桌面客户端
查看>>
exchange online 用户许可证迁移常见问题
查看>>
ELK调优
查看>>
mysql性能优化2
查看>>
【Java】Java 实现导出excel表 POI
查看>>
如何对待用户需求的几点思考
查看>>
POJ 3686 The Windy's 最小费用最大流
查看>>
RH124-13 软件包安装与升级
查看>>
我的友情链接
查看>>
1.python入门到精通
查看>>
通过vue-cli来学习修改Webpack多环境配置和发布问题
查看>>
Exchange Server 2013 高可用部署系列(四)邮箱服务器高可用——数据库可用性组(DAG)...
查看>>
和尚挑水的故事给我们带来的思想
查看>>