x86下的并行编程可能很难,尤其是在多核CPU下。 假设我们有多核x86 CPU和更多不同的多线程通信组合。
哪个模型更好(更有效)锁定共享内存区域:测试&设置或测试和测试&设置以及何时使用它!
这里我在x86汇编程序的Delphi IDE下编写了两个简单(没有时间限制)的测试程序:
procedure TestAndSet(const oldValue, newValue: cardinal; var destination);
asm
//eax = oldValue
//edx = NewLockValue
//ecx = destination = 32 bit pointer on lock variable 4 byte aligned
@RepeatSpinLoop:
push eax //Save lock oldValue (compared)
pause //CPU spin-loop hint
lock cmpxchg dword ptr [ecx], edx
pop eax //Restore eax as oldValue
jnz @RepeatSpinLoop //Repeat if cmpxchg wasn't successful
end;
procedure TestAndTestAndSet(const oldValue, newValue: cardinal; var destination);
asm
//eax = oldValue
//edx = NewLockValue
//ecx = destination = 32 bit pointer on lock variable 4 byte aligned
@RepeatSpinLoop:
push eax //Save lock oldValue (compared)
@SpinLoop:
pause //CPU spin-loop hint
cmp dword ptr [ecx], eax //Test betfore test&set
jnz @SpinLoop
lock cmpxchg dword ptr [ecx], edx
pop eax //Restore eax as oldValue
jnz @RepeatSpinLoop //Repeat if cmpxchg wasn't successful
end;
修改
英特尔在文档中提到了两种方法测试和设置或测试&测试和设置。我不会在哪种情况下建立更好的方法,所以何时使用它。检查:Intel
答案 0 :(得分:3)
当然第一个(testAndSet)更好,因为使用cmp& amp;重复测试时第二个没有达到太多jnz - 介于两者之间。当您这样做时,目标值可能会因为未锁定而改变。
答案 1 :(得分:2)
TTAS(#2)是一种很好的做法。在CAS
之前“潜伏”并等待“机会”是Java和.NET并发类中的常见做法。话虽如此,cmpxchg
在过去几年中得到了很多优化,所以你可能会在最新的处理器上获得几乎相同的结果。
在这两种情况下你应该尝试的是,当你旋转时使用一些exponential backoff。
<强>更新强>
@GJ:你应该找到some more up-to-date documentation on Intel's site。请注意自486以来没有锁定总线的段落以及xchg
和cmpxchg
的对比图表,它们显示它们实际上是相同的。
在locked
指令上旋转读取仍然是一个好主意,以避免在独占模式下获取缓存行时出现一些争用。 (所以TTAS。)
然而,只有在您实施的情况下,这才能提供有用的增益。指数后退,甚至在一段时间后产生CPU。
如果您使用的是单个现代多核CPU,并且核心之间具有共享L3缓存,那么TTAS和TAS之间的差异,或者没有退避将会更小,如果您使用多核,则会更加明显插座 - 例如服务器 - 机器或核心之间没有共享缓存的多核CPU。根据争用的数量,它们也会有所不同。 (即轻载会看到TTAS / TAS之间的差异较小。)
答案 2 :(得分:2)
我会使用第二个approch,一个没有锁定的测试,然后在测试过程中锁定,并提出一些建议:
在所有情况下,我认为你会更好: