何时使用Test& Set或Test& Test& Set?

时间:2010-11-28 23:05:03

标签: multithreading delphi x86 parallel-processing

x86下的并行编程可能很难,尤其是在多核CPU下。 假设我们有多核x86 CPU和更多不同的多线程通信组合。

  1. 单一作家和单一读者
  2. 单读者多位作家
  3. 多位读者和单一作家
  4. 多位读者和多位作家
  5. 哪个模型更好(更有效)锁定共享内存区域:测试&设置测试和测试&设置以及何时使用它!

    这里我在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

3 个答案:

答案 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以来没有锁定总线的段落以及xchgcmpxchg的对比图表,它们显示它们实际上是相同的。

locked指令上旋转读取仍然是一个好主意,以避免在独占模式下获取缓存行时出现一些争用。 (所以TTAS。)

然而,只有在您实施的情况下,这才能提供有用的增益。指数后退,甚至在一段时间后产生CPU。

如果您使用的是单个现代多核CPU,并且核心之间具有共享L3缓存,那么TTAS和TAS之间的差异,或者没有退避将会更小,如果您使用多核,则会更加明显插座 - 例如服务器 - 机器或核心之间没有共享缓存的多核CPU。根据争用的数量,它们也会有所不同。 (即轻载会看到TTAS / TAS之间的差异较小。)

答案 2 :(得分:2)

我会使用第二个approch,一个没有锁定的测试,然后在测试过程中锁定,并提出一些建议:

  • 使用调用SwitchToThread而不是暂停
  • 在非锁定重复cmp循环中调用SwitchToThread
  • 仅在cmp / lock失败时才调用SwitchToThread

在所有情况下,我认为你会更好:

  • 使用Windows API进行同步,如果您真的想要在项目中处理低级同步,请参阅Synchronization Functions on MSDN - Microsoft为您做了低级别和优化工作。大多数这些调用都是优化的asm代码,在用户模式下运行,因此非常快。
  • 使用高级多线程框架,在实践中将为您处理所有这些问题,并且最终可以很好地扩展 - 请参阅Delphi OmniThreadLibrary
  • 使用专用的内存管理器,例如NexusMMTBBMMScaleMM / SynScaleMM