首页 » Java程序员修炼之道 » Java程序员修炼之道全文在线阅读

《Java程序员修炼之道》6.3 哪里出错了?我们担心的原因

关灯直达底部

在几年前,性能问题看起来并不重要。时钟速度不断上升,所有软件工程师只要多等几个月,哪怕是写得很烂的代码,也能借助节节攀升的CPU速度表现出优异性能。

那么,事情怎么会错得这么离谱?为什么时钟速度的提升不再那么快了?更让人担忧的是,为什么有3GHz芯片的电脑看起来比2GHz芯片的快不了多少?行业中软件工程师需要考虑性能问题的这种趋势是从哪里来的?

我们会在本节讨论引导这股趋势的力量,以及连最纯粹的软件开发人员也需要了解一点硬件知识的原因。我们会为本章剩下的内容打好基础,让你理解JIT编译的概念和一些有深度的例子。

你可能听说过“摩尔定律”。很多开发人员都知道这个定律与提高计算机速度有关,但对于具体细节不甚了了。我们来解释一下它到底是什么意思,以及它在不久的将来可能带来的影响。

6.3.1 过去和未来的性能趋势:摩尔定律

摩尔定律是Gordon Moore提出来的,他是Intel的创始人之一。该定律最常见的形式之一是:晶片上的晶体管数量每两年翻一番是合算的。

这个定律实际上是对计算机处理器(CPU)发展趋势的一种看法,基于Gordon Moore在1965年发表的一篇论文,最初是对未来10年进行预测——也就是直到1975年。而这一预测直到现在仍然能够应验(据预测其在2015年以前都有效),实在值得称道。

我们在图6-2中绘制了Intel x86家族从1980年发展到2010年的i7整个历程中的一些真实CPU。图中显示了不同时期发布的芯片所集成的晶体管数量。

图6-2 随时间变化的晶体管数量对数线性图

这是一个对数线性图,所以y轴上的每次增长都是前一个值的10倍。如你所见,这条线基本是直的,而且每隔六或七年就会穿过一个垂直层级。这证明了摩尔定律的准确性,因为六或七年增长十倍和每隔两年翻一番基本是一致的。

图中y轴的刻度是对数,这就是说Intel在2005年生产的主流新品大概有一亿个晶体管。这是1990年生产的芯片上的晶体管数量的100倍。

一定要注意摩尔定律特别谈到了晶体管数量。要知道,单凭摩尔定律不足以让软件工程师继续从硬件工程师那里得到好处,理解这一点是最基本的要求。

注意 晶体管数量和时钟速度不是一回事儿,人们一般会认为更高的时钟速度就意味着更好的性能,其实这是一种过于草率的想法。

摩尔定律在过去很有指导意义,并且在未来一段时间内应该仍然准确(有不同的估算,但在2015年前看起来是合理的)。但摩尔定律计算的是晶体管数量,用这个数值来指导开发人员提高代码性能越来越不靠谱。实际上,你会发现情况要更复杂。

在实际操作中,性能是由一系列因素共同决定的,而且每个因素都很重要。然而如果硬让我们从里面挑一个,应该是定位指令与数据运行速度的相关性。这个概念对性能非常重要,我们会深入了解。

6.3.2 理解内存延迟层级

计算机处理器要处理数据。如果它要处理的数据到不了,CPU时钟多快都没用——它只能等着,执行空操作(NOP),在数据到来之前基本就处于停转状态。

也就是说在解决延迟时,最重要的两个问题是,“CPU核心要处理的数据的最近的复本在哪里?”,还有“把它送到核心能用的地方需要多长时间?”主要有以下几种答案。

  • 寄存器:这是CPU上的内存地址,随时可用。这部分内存是指令直接操作的。

  • 主存:这一般是DRAM。访问时间在50纳秒左右(关于如何使用处理器缓存避免这段延迟请参见后续内容)。

  • 固态磁盘(SSD):访问这种磁盘所需的时间不足0.1毫秒,但跟传统硬盘比起来,它们要便宜一些。

  • 硬盘:访问这种磁盘并把数据加载到主存中大概需要5毫秒。

摩尔定律预测了晶体管数量的指数级增长,这对内存也有好处——内存访问速度也能以指数级增长。但这两种指数并不相同。内存速度的提升比CPU晶体管数量的增长要慢得多,这意味着核心迟早会因为没有相关数据可以处理而落入空闲状态。

为了解决这个问题,在寄存器和主存之间引入了缓存。缓存是少量更快的内存(SRAM,而不是DRAM)。这种更快的内存成本要比DRAM高很多(无论是金钱还是晶体管),所以计算机不全用SRAM作为内存。

缓存分为一级缓存(L1)和二级缓存(L2)(某些机器还有L3),数值表明缓存到CPU的距离(越近越快)。我们会在6.6节(在JIT编译上)详细讨论缓存,并给出一个例子来表明L1缓存对运行代码的重要影响。图6-3展示了L1和L2缓存比主存快多少。之后,我们还会给出一个例子来阐明这些速度差异对运行代码的性能有什么影响。

图6-3 寄存器、处理器缓存和主存的相对访问时间

除了增加缓存,20世纪90年代到21世纪早期大量使用了另外一种技术解决内存延迟的问题,就是增加处理器的功能,这使得处理器越来越复杂。即便CPU处理能力和内存延迟之间的差距越来越大,也仍然采用复杂的硬件技术来保证CPU有数据可以处理,比如指令级并行(ILP)和芯片多线程(CMT)。

这些技术的出现消耗了CPU晶体管预算中的大部分,并且它们使真实性能收益递减。这一趋势导致了新观点的出现,即在未来设计带有多个(或很多)核心的CPU芯片。

这意味着未来的性能和并发密切相关——主要办法之一就是通过拥有更多核心让系统整体性能得到提升。那样,即便有一个核心在等待数据,其他核心也可以继续工作。这种关系十分重要,所以我们要一再提起。

  • 将来的CPU是多核的。
  • 性能和并发绑在一起变成了相同的关注点。

Java程序员除了要关注硬件,还要注意JVM特性带来了额外的复杂性。下一节我们就来看一下这些内容。

6.3.3 为什么Java性能调优存在困难

在JVM或其他任何受控运行时环境上做性能调优天生就比在非受控环境下做调优困难。这是因为C/C++程序员几乎所有事情都要自己做。OS只提供很少的服务,比如基本的线程调度。

在受控系统中,基本观点是让运行时来控制环境,不用开发人员自己处理所有细节。这能提高程序员的生产率,但要放弃某些控制权。另外一种选择是放弃受控运行时提供的所有便利,但和性能调优所做的工作相比,这个代价实在太高了。

造成调优困难的平台特性主要是:

  • 线程调度;
  • 垃圾收集(GC);
  • 即时(JIT)编译。

这些特性能以很巧妙的方式交互。例如,编译子系统用计时器来决定编译哪个方法。也就是说等待编译的候选方法集可能会受到调度和GC等特性的影响。每次运行时所编译的方法可能都不同。

正如你在本节中看到的,准确测量是性能分析决策过程的关键。如果你决定认真对待性能调优,那么理解Java平台中处理时间的细节和限制就非常有用。