LongAdder
是一个巧妙设计的原子计数器,它应该在更新共享计数器时减少缓存线争用。问题是,它依赖于原子cas操作来实际更新计数(这是它与更通用的 LongAccumulator
和朋友)。
在amd64平台上(可能在其他地方),原子cas比原子add慢得多,至少在涉及到中等数量的核时是这样。因此,看起来,简单地对一个共享的 VarHandle
是一个更好的主意:代码更简单,而在一个普通的(大约8核)容器上,性能不会更差甚至更好。
正在使用 LongAdder
与…相比有什么好处 VarHandle
中介原子加法?
想了解更多情况:
默认情况下,java以cas循环的形式执行所有原子操作(甚至是简单的算术运算)(例如:https://github.com/openjdk/jdk/blob/64644a10725abb4bea8a947508999be6c67c52ed/src/java.base/share/classes/jdk/internal/misc/unsafe.java#l2468). 如果jit愿意的话,它可以自由地将内部函数升级到更好的东西。 LongAdder
,作为一个分片cas循环,肯定比任何简单的cas循环都快,即使在低争用级别上也是如此。
然而,似乎“每个cpu一个cas碎片”场景( LongAdder
对于任何争用级别,都比amd64上的实际硬件原子加法(“lock xadd”指令)慢一些。通过大幅度增加碎片的数量(至少2倍的cpu数量,在大的倍数(如8倍或更高)上可以实现额外的增益),可以加快速度。
不过,对一个适当的原子计数器进行简单的切分(即用硬件加法代替cas的计数器)仍然会更快、更简单。
1条答案
按热度按时间ztmd8pv51#
LongAdder
设计用于在写容量大的场景中更好地扩展,而不是AtomicLong
在任何争论的层次上,除了没有,确实是这样。如果你的场景不是写得很重,那么
AtomicLong
会更快。c2编译器总是替换
@HotSpotIntrinsicCandidate
-带有硬编码程序集的带注解的方法,而不仅仅是在感觉这样做的时候。lock xadd
比cas retry循环增加值快。LongAdder
比…快lock xadd
,因为它在线程之间拆分计数器。它创建多个计数器,因此减少了争用。