原子memcpy建议

时间:2011-07-15 08:14:12

标签: performance scalability atomic memcpy


在测试程序的可伸缩性时,我遇到了必须将memcpy操作作为原子操作的情况。我必须将64字节的数据从一个位置复制到另一个位置。
我遇到了一个解决方案,即使用旋转变量是:

struct record{
    volatile int startFlag;
    char data[64];
    volatile int doneFlag;
};

和伪代码如下

struct record *node;
if ( node->startFlag ==0 ) {  // testing the flag 
    if( CompareAndSwap(node->startFlag , 0 ,1 ) ) {  // all thread tries to set, only one will get success and perform memcpy operation 
        memcpy(destination,source,NoOfBytes);
        node->doneFlag = 1; // spinning variable for other thread, those failed in CompAndSwap 
    }
    else {
         while ( node->doneFlag==0 ) { // other thread spinning 
          ; // spin around and/or use back-off policy  
         }
   }}

这可以作为原子memcpy执行吗?虽然如果执行memcpy的线程被抢占(在memcpy之前或之后但在设置doneFlag之前),那么其他人将继续旋转。或者可以做些什么才能使这个原子化。
情况就像其他线程必须等待,除非数据被复制,因为它们必须与插入的数据进行比较,并使用自己的数据。
我在startFlag的情况下使用test-and-test-and-set方法来减少一些昂贵的原子操作。 自旋锁也是可扩展的,但我已经测量过原子调用比自旋锁具有更好的性能,而且我正在寻找这个代码片段中可能出现的问题。 因为我使用自己的内存管理器,所以内存分配和免费调用对我来说代价很高,所以使用另一个缓冲区并在其中复制内容,然后设置指针(因为指针大小在原子操作下)是昂贵的,因为它会需要很多mem-alloc和mem-free调用。

编辑我没有使用互斥锁,因为它们似乎不是可扩展而且这只是程序的一部分,因此关键部分不是很小(据我所知,对于较大的临界区,很难使用原子操作)。

4 个答案:

答案 0 :(得分:5)

您的代码段肯定会被破坏。在node-> startFlag

上有一场比赛

不幸的是,没有原子方法可以复制64个字节。我想你在这里有很多选择。

  1. 以原子方式访问节点 - > startFlag。我写了几篇关于这个主题的帖子:herehere
  2. 使用用户模式自旋锁保护整个事物。 Here's a post on the subject
  3. 使用RCU之类的方法。你可以阅读RCU here。换句话说,我们的想法是使用指针引用要复制的缓冲区。然后你做:
    1. 分配新缓冲区。
    2. 创建它的内容(来自您的来源的memcpy)。
    3. 用新的原子替换缓冲区。
    4. 等待所有访问旧缓冲区的线程过期并释放它。
  4. 希望它有所帮助。 亚历克斯。

答案 1 :(得分:2)

使用同步机制。互斥体似乎是合理的。

如果您担心可扩展性,请尝试使用显示器。

答案 2 :(得分:2)

现在已经很晚了,但是对于其他人来说这个问题,以下内容更容易,缓存更少。

注意:我将CAS更改为GCC中相应的原子内置。不需要“易失性”,CAS引入了内存屏障。

// Simpler structure
struct record {
    int spin = 0;
    char data[64];
};



struct record *node;

while (node->spin || ! __sync_bool_compare_and_swap(&node->spin , 0 , 1)); // spin
memcpy(destination,source,NoOfBytes);
node->spin = 0; 

PS:我不确定CAS而不是node-> spin = 0是否可以提高效率。

答案 3 :(得分:1)

不要使用锁,请使用 CriticalSection 。锁是重量级的,CriticalSections非常,非常快速(根据平台只有几个指令)。您没有指定操作系统,我在这里发布的信息在Windows中有经验,但其他操作系统应该类似。

如果CriticalSections包含大量代码,那么您是否担心CriticalSections可能可扩展 根本原因(可能是你读到的地方的论点)是,如果线程长时间保持在CS上,则CriticalSection不能在多个线程中交错。你可以通过仅将CS包装在真正需要原子的那部分代码中来避免这种情况。另一方面:如果您使用CS 太细粒度百分比开销当然会增加。通过任何同步都无法避免这种权衡

你说你需要的原子操作是64字节的副本:在这种情况下,CS的同步开销将可忽略不计。就试一试吧。使用您同步的粒度(围绕64个字节的单个副本或大约4个这样的副本),您可以通过进行一些实验来平衡线程交错粒度与百分比开销。但总的来说:CS足够快且足够可扩展。