Java8 性能提升: LongAdder vs AtomicLong

jopen 10年前

即将到来的Java8给在Java虚拟机上最广泛使用的语言带来了大量的新特性。或许最多提到的是Lambdas表达式,Scala和JRuby的爱好者们对此发出了终于来了的感叹。但是对于多线程应用更为重要的类是新增的LongAdder和DoubleAdder,在多线程下,原子实现比 AtomicInteger和AtomicLong提供了更优越的性能。
一些简单的指标阐述了AtomicLong和LongAdder之间的性能不同点——测试下面这些指标,我们使用了可以访问Intel Xeon E5-2670所有8个核的m3.2xlarge EC2实例。

a7b44377c145b6febafb5a41a6e7dda4.png
在单线程下,新的LongAdder慢了1/3,但是当多个线程在争着增加字段,LongAdder则显示出了自己的价值。注意到,每个线程所做的唯一事情就是试图增加计数——这是一个最极端形式的综合标准。这里的竞争比你在实际应用程序中可能看到的要高的多,但是有时候你确实需要这种共享计数器,而 LongAdder带来了很大的帮助。

你可以在我们的 java-8-benchmarks 仓库中找到这些基准测试程序数值的代码。它使用了JMH来完成实际工作,并以Marshall的gradle-jmh-demo为基础。JMH能够为程序员完成对精度要求很高的工作,并有效降低编程的难度,保证了结果数据能够反应目前在基于JVM性能测试精度的较高水准。JMH并非一定在perf下运行,所以我们还做了一些单例测试。

Perf-stat下更多的细节

通过单例测试,我们可以在perf-sata下对测试程序有更多的控制力度,以及获取程序运行中更多的细节。最基本的指标是每一个标准运行占用的时间。这些基准都在运行在英特尔I7-2600k内核上(实际硬件,不是虚拟的)。

在单线程的情况下,AtomicLong稍微快一点,在2个线程的时候,AtomicLong比LongAdder慢近4倍,而在线程数和机器CPU核数一致的情况下,AtomicLong比LongAdder慢近5倍。

让人印象更深刻的是,直到线程数超过CPU的物理核数(在这个例子中是4)之前,LongAdder的性能一直是个常量。

一个周期内的指令

周期内的指令衡量标准是:”CPU运行指令的时间” 对比 “CPU等待加载内存或者处理高速缓存加载或匹配数据的时间”。在这个例子中,我们看到Atomic在多线程的情况下IPC(周期内的指令)指标非常的差,而LongAdder维持着一个更健康的IPC(周期内的指令)指标. 从4线程到8线程之间的下降可能是因为CPU有4个核,每个核中有2个硬件线程, 而硬件线程在这个情况下没有实际上的帮助。

空闲时间
处理器上的执行流水线主要分为2个部分:负责获取和解码操作的前端,和负责执行指令的后端。获取的操作没有什么值得讨论的,所以我们跳过前端。

后端揭示了更多幕后的情况,AtomicLong的实现在后端留下了比LongAdder几乎一倍的周期闲置。AtomicLong的IPC较低和它的高闲置时间是直接相关的:CPU内核花费了大量时间来决定它们中间的谁去控制包含AtomicLong的缓存线。

参考阅读

原文链接: palominolabs 翻译: ImportNew.com - 吴功伟
译文链接: http://www.importnew.com/9560.html