摘要:有一句名言计算机科学中的任何问题都可以通过加上一层间接层来解决,一层不够就再加一层。计算机科学的确是靠着一层又一层的抽象与封装解决了巨量的问题。
David John Wheeler有一句名言“计算机科学中的任何问题都可以通过加上一层间接层来解决”,一层不够就再加一层。后半句是我加的 (* ̄︶ ̄) ,虽然有点玩笑的意思,但是也的确能说明一些问题。计算机科学的确是靠着一层又一层的抽象与封装解决了巨量的问题。
我们来简单回顾一下:
开始的时候是程序员直接输入二进制指令来操纵硬件的,不仅性能低下还很耗费用户时间;
于是后来出现了操作系统,用文件、进程与线程、地址空间抽象了磁盘、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),直接运行这段代码得到结果1是4594 us,avg这个级别,在结果1基础上把//*****一行取消注释得到结果2是3916 us,avg 这个级别,在结果1基础上运行参数加上-XX:-RestrictContended使得@Contended起作用就能得到结果3是3466 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兴叹了。
访问原文,来自MageekChiu。
文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。
转载请注明本文地址:https://www.ucloud.cn/yun/69638.html
摘要:是一个典型的模式架构,用户通过终端将字符流传递给。仿照的工作原理,我们在协议之上设计了,见下图真实实现中,是应用层的通讯协议。僵尸进程问题我们知道由于缺少而导致僵尸进程无法回收的问题迄今存在。进入构建环境执行命令。 Coding WebIDE 做个国内首个基于 Docker 技术的云端开发平台于4月1日正式上线。本文主要和大家分享和探讨 Docker 在 Web IDE 中运用的一些经...
摘要:只能在不同的时候选用不同的假设和不同的理论来解释问题,许来西的文章讲到科学一定程度上通过放弃一贯性换取了实用性,放弃自洽性换取了它洽性。然而遗憾的是本身只提供了模块和洋葱模型的最小封装。 在写干货之前,我想先探(qiang)讨(diao)两个问题,模式的局限性?模式有什么用? 最近看到一篇文章对我启发很大,许来西在知乎的回答《哲学和科学有什么关联?》,全篇较长,这里摘录我要引出的一点:...
摘要:首先是最顶层的抽象,这个里面最基础的就是和,记忆中和的抽象是类似的,将计算结果和偏导结果用一个抽象类来表示了。不过,本身并没有像其它两个库一样提供,等模型的抽象类,因此往往不会直接使用去写模型。 本文将从deep learning 相关工具库的使用者角度来介绍下github上stars数排在前面的几个库(tensorflow, keras, torch, theano, skflow, la...
摘要:李飞飞花名飞刀,阿里巴巴集团副总裁,高级研究员,达摩院首席数据库科学家,阿里云智能事业群数据库产品事业部负责人,杰出科学家。是阿里云的云原生数据库,目前已有非常深厚的技术积累。 阿里妹导读:云计算大潮来袭,传统数据库市场正面临重新洗牌的情境,包括云数据库在内的一批新生力量崛起,动摇了传统数据库的垄断地位,而由云厂商主导的云原生数据库则将这种改变推向了高潮。 云时代的数据库将面临怎样的...
阅读 1827·2023-04-26 00:59
阅读 3128·2021-11-15 18:10
阅读 3071·2021-09-22 16:02
阅读 765·2021-09-02 15:15
阅读 3715·2019-08-30 15:56
阅读 1916·2019-08-30 15:54
阅读 2857·2019-08-29 16:31
阅读 2033·2019-08-29 16:10