在各种平台上Interlocked.Increment(ref x)
比整数和长整数更快还是更慢?x++
6 回答
它较慢,因为它强制动作以原子方式发生,并且它充当内存屏障,消除了处理器围绕指令重新排序内存访问的能力。
当您希望操作在可以在线程之间共享的状态上是原子的时,您应该使用 Interlocked.Increment - 它并不打算完全替代 x++。
根据我们的经验,Windows 上的 InterlockedIncrement() 等具有相当大的影响。在一个示例案例中,我们能够消除联锁并改用 ++/--。仅此一项就将运行时间从 140 秒减少到 110 秒。我的分析是互锁强制内存往返(否则其他内核怎么能看到它?)。L1 高速缓存读/写大约 10 个时钟周期,但内存读/写更像 100。
在这个示例案例中,我估计递增/递减操作的数量约为 10 亿次。所以在 2Ghz CPU 上,这对于 ++/-- 来说是 5 秒,对于互锁来说是 50 秒。将差异分散到多个线程中,接近 30 秒。
想一想,你会意识到Increment
调用不会比增量运算符的简单应用更快。如果是这样,那么编译器对增量运算符的实现将在Increment
内部调用,并且它们会执行相同的操作。
但是,正如您通过自己测试看到的那样,它们的表现并不相同。
这两个选项有不同的目的。一般使用增量运算符。当您需要操作Increment
是原子的并且您确定该变量的所有其他用户也在使用互锁操作时使用。(如果他们不是都合作,那么它并没有真正的帮助。)
它更慢。但是,这是我所知道的在标量变量上实现线程安全的最常用的方法。
它总是会更慢,因为它必须执行 CPU 总线锁定而不是仅更新寄存器。然而,现代 CPU 实现了接近寄存器的性能,因此即使在实时处理中也可以忽略不计。
我的性能测试:
挥发性:65,174,400
锁定:62,428,600
联锁:113,248,900
TimeSpan span = TimeSpan.FromSeconds(5);
object syncRoot = new object();
long test = long.MinValue;
Do(span, "volatile", () => {
long r = Thread.VolatileRead(ref test);
r++;
Thread.VolatileWrite(ref test, r);
});
Do(span, "lock", () =>
{
lock (syncRoot)
{
test++;
}
});
Do(span, "interlocked", () =>
{
Interlocked.Increment(ref test);
});