Java工程师要懂的硬件知识-前言


Mechanical Sympathy这个短语描述了一种车手对汽车天生的感觉,也是Martin Thompson大牛的博客标题。从并发编程网Disruptor的介绍中注意到这个短语,再去品位Martin对它的简短阐述’Hardware and software working together in harmony’的确很有道理。在对任何语言的深入学习研究中,总逃不过对底层硬件的了解与学习,很多语言的特性、行为在硬件的角度去观察就很容易解释了;同时在追求语言的更高性能的过程中,也要更多去了解硬件的知识,让软件更加匹配硬件的特性、更好利用硬件的优化才能获得更高的优化效果。

概述硬件工程师的杰作

互联网的繁荣是建立在硬件工程师的伟大杰作之上的。使硬件更加高效的一个途径是提高硬件的运行速度,另外一个途径就是让可以任务并行起来;随之而来的就是缓存、并发、同步等一些列设计和优化。现代计算机的步特性加之这些优化使得软件系统在多线程的情况下常常出现背离程序直觉的情况。作为一个高级语言开发者了解硬件就是为去感受Mechanical Sympathy,去了解硬件工程师的用心良苦从而让软件与硬件更加匹配。

讲故事要从开头说,在很早很早以前有一位叫图灵的先生画了一个盒子,这个盒子就一直把人们圈到了现在,这就是图灵机模型。

Turing Machine

这个盒子有一条纸带和一个规则表格还有一个内部状态存储,当然还有一个用来读、写纸带的读写头。整个过程模拟了人类在算草纸进行运算的过程:在纸上写或擦除一个符号;将注意力从一个位置转移到另外一个位置。

言归正传虽然现代计算器体系还在这个圈圈之中,但是整体的结构已经变得极度复杂了。 下图是一个CPU的逻辑组成(物理上并不是都在CPU里),它作为整个计算机系统的大脑,负责着处理所有类型数据的运算工作(其实还有各式各样的协处理器帮忙),这也是软件工程师关注的计算机系统核心模型。它主要有CU,MU,ALU,IO四个子系统组成,看似简单的四个框框其实每个都涵盖了N个复杂的结构。

img

Memory Unit

简单说内存和存储单元(MU)就是负责保存各种数据的部分,它也称为内部存储/主存储/RAM。RAM的名称来自其硬件特性随机访问,内部存储/主存储的名称来自相对于硬盘等通过IO系统访问的存储而言的。注意这里说的是CPU的逻辑组成MU指的并不仅仅是CPU内部的存储部件还应该涵盖了内存。

MU负责存储的有以下数据:

  1. 处理过程中,所需的所有数据和指令
  2. 处理过程中,产生的中间结果
  3. 已处理完成,但尚未通过输出设备发布的数据
  4. 经输入/输出设备,要进入/输出的所有主内存数据

MU功能看似简单但这一块却是Java工程师最需要关注的部分。众所周知各种存储部件的速度有很大差别,为了匹配高速运行的运算核心并很好的平衡成本,硬件工程师在这里设计了多层的缓存系统。也因为存储系统及IO系统的延迟,为了更好的发挥硬件能力,硬件工程师在这里设计了指令乱序、回写缓冲等等。

先给出一张Intel的Sandy Bridge微架构处理器的MU示意图,让大家对MU的缓存系统组成和各个部分的访问速度有一个直观的印象。

img

从图中看到整个MU由每个核心独享寄存器,读取缓冲存储缓冲,L1 Cache,L2 Cache同插槽核心共享L3 Cache以及一个跨插槽的NUMA结构组成。 NUMA是每个插槽独享内存控制器(MC)MC分配到的内存和连接插槽通信的QPI总线组成的。

在这些不同核心的缓存之间存在着硬件连线,通过MESI/MESIF/MOESI协议,QPI总线HT总线保证缓存的一致性。现在网上经常出现对这一块误解的文章,从缓存不一致角度去解释多线程的某些一致性问题;新手要认识到MU的这些缓存在硬件上都已经保证了一致性不要被误导。

图中标注了每种存储访问的延迟,从最小的1个CPU节拍到跨插槽内存访问时QPI延迟+DRAM访问延迟大致100ns左右的延迟,各级访问速度的差异显而易见。这里的优化一般考虑尽可能命中缓存、降低缓存间一致性协议通信流量的方案。

直观的看对于一颗频率为3G的CPU核心,因为流水线(Pipeline)技术每CPU节拍可以[并行执行]1[4条指令]2;1ns就有3个节拍也就是每颗核心能并行执行12条指令,可以想象跑一趟主存就可能要耽误1200条指令,而且这个延迟还从10-100纳秒不一定。在等待这个延迟的时候CPU就什么都不做吗?硬件工程师在这里设计了乱序执行,这样就可以很大限度的隐藏这个延迟了。

由此可见这里对于软件工程师提升性能有多大的发挥空间,软件工程师的Mechanical Sympathy就是在这些细节中展现出的力量。相反这里提到的乱序能很大隐藏延迟,但它也是无数程序员都踩过的深坑,在多线程编程中迷糊了无数人。天下没有免费的午餐!作为一个Java工程师去学习硬件知识可以更好的理解多线程编程、可能大大提升程序的响应速度,但如果平时工作中太纠结于这些硬件细节又会迷失忘却作为一名高级语言开发者最重要的开发效率。

ALU(Arithmetic Logic Unit)

算数逻辑运算单元(ALU)由算数运算单元(AU)和逻辑运算单元(LU)组成。

算数运算单元主要完成各种数学运算,也就是加、减、乘、除等运算。在这一块的优化一般就是推荐尽量使用AU擅长的加法、移位运算,比如使用移位运算代替乘法运算、在二维坐标系求两点距离尽量直接使用未开方运算的数值等。

上述内容在组原教材中有明确的记载大家也很理解和认同。的确在早期的CPU中乘法运算相对加法运算非常耗时,但是随着技术的发展越来越多的CPU整合了数学协处理器,现在差距可能不是那么悬殊了。协处理器就好比CPU的一个小弟,因为其电路是专为某种行为设计的所以在特定操作上比通用处理器速度更快。当CPU遇到某个复杂的运算时会将这个运算交给协处理器完成从而提升整体的速度。这里提到协处理并不是说我们没有必要进行优化了,而是提醒刚刚毕业的小鲜肉们技术日新月异,有的时候我们的教材不一定适用了不要墨守成规,像这样的例子更极端的还有Java(32位JVM测试)里面short运算比int运算更慢。

逻辑运算单元(LU)就是完成逻辑运算的组件,例如比较、判断、匹配及归并数据(comparing, selecting, matching and merging of data)。

Control Unit

控制单元(CU)负责操作计算机的所有部件协调的工作,它并不直接参与到数据处理、读写过程而是指挥、协调组件完成数据处理。

CU主要完成以下功能:

  1. 控制数据流和指令流在其它系统中的流转
  2. MU中获取指令、翻译指令并根据指令操作计算机的完成指令
  3. 控制和协调其它单元的工作
  4. 控制IO单元进行数据的输入输出

作为一个Java工程师本人对这一块的了解也不是很深入,写在这里也是希望抛砖引玉盼望有朋友能给出一两个这一块优化的实例。

IO系统

IO系统负责着与外部设备通信,对于Java工程师最常见的就是文件系统访问、数据库访问、网络服务等。这一块延迟相对MU延迟可能是非常大数字,所以作为一个Java工程师这也许是我们使用多线程编程最重要的原因,让程序在IO等待期间并行起来充分利用系统的计算能力。配合多线程还有批量处理、NIO等技术,总之一个应用响应时间出现问题最常见的就是IO占用的时间比非常高而且还是阻塞等待。

小节

本节非常概括的介绍了CPU基本逻辑组成和一些技术名词;一方面是希望使大家在后续讨论中对这些名词不是很陌生,另外主要的目的还是为新手提供一个参考的概览图,希望在后续讨论过各种局部技术后能为其形成自己的知识体系有一点帮助。

限于个人技术能力、见识限制,还望各位看官不吝赐教。

参考

http://www.tutorialspoint.com/computer_fundamentals/computer_cpu.htm

https://www.doc.ic.ac.uk/~wl/teachlocal/arch2/

http://ifeve.com/from-javaeye-cpu-cache/

http://ifeve.com/cpu-cache-flushing-fallacy-cn/


  1. 注意是并行执行4条指令而不是完成4条指令 

  2. 4条指令这个数字是教材数据并不准确实际可能为流水线级数(PipeLine Stage),Intel早就达到了21级流水线所以这个数字可能大于等于21。 

本文采用CC BY-SA许可发布,您可以自由的转载分享。

转载请保留出处 BeanMr.
http://blog.beanmr.com/2016/01/14/a-hardware-view-for-java-preface/