精彩的算法,优秀的编程技巧一直广受开发者的喜爱。然而开发者不懂系统赖以运行的硬件工作方式怎么可以?基于业内一直充斥着新型硬件已经复杂到难以理解的流言,高性能计算专家Martin Thompson发表了主题为《Mythbusting Modern Hardware to Gain "Mechanical Sympathy"》的演讲。他认为如果你可以理解像Hibernate这样复杂的框架,去弄清程序赖以运行的硬件工作模式实在是太简单了。 以下为译文: Martin Thompson是位高性能计算专家,一直从事帮助开发者弄清新型计算系统的内部构造。他在Cache、Buffer、Memory Controller、Processor Architectures、Cache line等领域发表过多次讲话和教程。 Thompson认为当下开发者在系统运行的理解上并没有投入应有的精力,大家往往被有趣的和时尚的技术所吸引。他还认为与其传授开发者编程策略,何不让其具备自己发掘的能力。他认为当下的形势非常诡异:程序员花很多的时间去理解像Hibernate这样复杂的框架,而不是花很少一点努力去弄清程序赖以运行的硬件是如何工作的。 “通过实践观察,而不是盲目发言”一直是Martin倡导的思想之一,所以给自己的演讲《Mythbusting Modern Hardware to Gain "Mechanical Sympathy"》选择一个MythBuster(流言终结者)的角色并不奇怪。在演讲中Martin抨击了“新型硬件复杂度已经让普通开发者无法理解”这个论调,并从CPU、内存、网络、存储I/O等多个方面剖析了新型硬件架构。期间他阐述了赛车手Jackie Stewart提出的"Mechanical Sympathy"这个术语:赛车手想获得更好的成绩,那么必须对汽车的运作有一个很好的了解,驾驶员必须与赛车配合的足够默契才能获得赛车的最佳性能。Martin将这个观点延伸到了开发领域,认为开发者需要清楚硬件的工作模式,才能更好的奴驾基础设施性能。如果你可以搞清楚Hibernate,就可以搞清楚任何事情。 讲话中最精彩的部分就是有些我们一直认为做随机访问的设备,比如内存、硬盘、SSD在特定的情况下会转换成串行设备。比如说磁盘,其实就是一个又大又快的磁带,其实它并不是真正意义上的随机访问。 流言1:CPU并没有变的更快 根本原因在于CPU不能更加赖热,而不是不能变快。我们让CPU计算的更快,他们就会变的更热,并且这种局部散热是非常困难的。 时钟速度不代表一切。举个例子,给Alice和Wonderland两个文档做word split,Intel Core 2 Duo,2008,2.40GHz处理器可以达到每秒1434次操作。Intel Core,2011,2.20GHz处理器可以达到每秒2674次操作;我们看到虽然时钟速度变慢了,但是速度几乎是提升了两倍,并且这个趋势在继续发展。 取代继续变快,Sandy/Ivy Bridge平台的CPU内部使用了并行处理。比如使用3 ALU及6 Port的CPU用来加载和存储数据,我们就需要更多的Port来供给ALU,这样就可以达到每个周期并行处理6条指令的效果。 当然这里还存在多分支和多步解决的问题。多分枝或者拥有除法的代码肯定没有使用简单连接词“+、-、*”来的快。 CPU拥有计数器,这样一来,我们就可以更直观的观察其性能。在Linux上使用pref stat访问计数器。 在Alice和Wonderland测试中使用Nehalem 2.8GHz运行pref stat,我们发现CPU空闲时间将达到1/3,这样的话更快的CPU其实并没有什么帮助。 而当使用Sandy Bridge 2.4GHz时,CPU的空闲时间也达到了1/4。实验证明快不在时钟速度上,而在指令被传送到CPU的速度。 流言2:内存提供的是随机读取 1. 这个问题我们考虑的根本其实是成本问题。因为主储存器非常昂贵,我们又想尽可能快的给CPU传送指令。那么我们该如何做?我们需要让数据尽可能的靠近CPU,然而新时代的寄存器到寄存器复制模式将没有任何开销,因为这里做的是重映射,甚至没有任何移动。 2. 缓存层。缓存是越大越缓慢,所以这是速度与成本的对抗,但是功率同样很重要。访问磁盘的功率永远要比访问L1缓存大得多。新型处理器已经着手将数据从网卡直接传输到缓存,这样跳过了内存,通过不涉及CPU来降低温度。 3. 在内存访问排序、缓存结构以及连贯性上有很多细节可谈。重点是内存的不同层以及CPU之间有非常复杂的环路。如果你不可以让内存的子系统、缓存、存储器总线足够的快,就无法足够快的给CPU传输数据,这也是CPU无法变快的原因。 4. 这就意味着软件必须以一个非常友好的方式访问内存,否则将会出现CPU饥饿的状况。在Sandy Bridge平台下了连续遍历内存需要在L1上花费3 clocks,L2上花费11 clocks,L3上花费14 clocks以及内存上的6ns。页内随机则分别是3 clocks,11 clocks,18 clocks以及22ns。全随机存储访问则是3 clocks,11 clocks,38 clocks以及65.8 ns。 5. 这是高效的内存连续遍历时间,所以我们如何访问内存将是非常、非常的重要。 6. 编写多分支代码将造成更多的指令丢失,因为这里有太多的数据需要跟踪。 7. 鉴于你不可能连续的遍历内存,你需要降低耦合以及增加连贯性好的耦合,良好的连贯性可以让这些变得非常高效。如果你的代码存在太多分支,并且都是运行在堆上,那么运行的速度将会相当的缓慢。 流言3:HDD提供的随机访问 在磁盘内圈和外圈写入时性能有着很大的区别,更多的扇区被分布在磁盘的外圈,这样可以得到非常高的密度。将更多的扇区放到外圈,从而获得更高的密度。对于旋转的磁盘来说,这样可以获得更高的吞吐量。 在1万转的磁盘上,外圈的连续读取速度可以达到220MB/S,而内圈的只可以达到140MB/S。 最快的磁盘转速只有1.5万,并且已在这个数字上停滞好多年了。 当光头移动到某个扇区时,磁盘会对这个扇区上数据进行预读取及重排序。为了能获取更多的数据,当下磁盘一个扇区的大小为4K,也就是说你在磁盘上写入或者读取1字节的数据,整个工作量会达到4K。 操作由什么组成: 指令处理——亚秒 寻道时间——服务器0-6毫秒,笔记本0-15毫秒 旋转延迟——对于一个1万转的硬盘,旋转一圈需要6ms的时间,那么平均的旋转时间大约为3ms。 数据传输——100-200MB/S 随机访问一个4K的块,延时时间是10ms或者是100IOPS。随机吞吐量每秒不会超过1MB,非常智能的硬件也不会超过2MB。这样再说磁盘是随机访问显然不切实际,如果你看到一些异常的数据,那只能说这些数据并没有真正的使用磁盘。 磁盘就是个非常大的磁带,显然不是真正意义上的随机访问。 流言4:SSD提供了随机访问 1. SSD里一个块通常的大小为2MB,由大量的单元组成。SLC(Single Level Cell)——单层单元,每个单元可以存储1个字节(1、0)。MLC(Multiple Level Cell)——每个单元可以存储2或3个字节。 2. 单个单元的操作开销是巨大的,所以你可以针对行进行操作。这里被称为页,一页的大小通常为4K或者8K。以页的大小进行读或者写是相当快的,因为这里通常没有移动的过程。 3. 删除最小单位是块。SSD工作的方式是针对每个单元进行写入,当你向某个单元中写入数据并删除旧字节时,会发生这样的情况:删除一个字节是容易的,因为只需要关闭这个单元,然而当你打开一个单元时,你必须要同时打开这个单元附近的单元,这样你就不可以准确的去操作某个单元,所以你必须同时删除整个块。介于SSD中每个块的写入和读取次数是有限的,并且你的要求并不是删除整个块,所以这里只是将这个字节标注为删除。总而言之,数据被标记为删除,然后向新的块写入数据。这是有开销的,同时介于每个块读写次数都有着上限,所以直到这块SSD彻底报废,需要不停的做垃圾回收及块压缩。 4. 举个例子,SSD的读写和可以达到200M/S。当你做删除时,其读性能依然能得到保证,但是写入的速度明显下降,这就是因为SSD的垃圾回收机制。如果某块SSD的写入性能下降太多,你就必须为其重新格式化,同样还存在小的写入引起一连串的复制事件。 5. SSD在连续和随机读取上有着相当不俗的表现,如果你只做append-only write的话,性能也会一直不错。 6. 在4K随机读写,40K IOPS的情况下,平均操作次数将达到100-300ms,将花去长达半秒的垃圾回收时间。 7. 局部的异变可能会导致极低的性能 |