【问题标题】:atomic memcpy suggestion原子内存建议
【发布时间】:2011-10-05 23:08:13
【问题描述】:


在测试程序的可伸缩性时,我遇到了必须将 memcpy 操作设为 atomic operation 的情况。我必须将 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 的情况下,我使用测试和测试和设置方法来减少一些昂贵的原子操作。 自旋锁也是可扩展的,但我测量到原子调用比自旋锁提供更好的性能,而且我正在寻找这个 sn-p 中可能出现的问题。 而且由于我使用自己的内存管理器,所以内存分配和免费调用对我来说代价高昂,所以使用另一个缓冲区并在其中复制内容,然后设置指针(因为指针大小是在原子操作下)是昂贵的,因为它会需要多次 mem-alloc 和 mem-free 调用。

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

【问题讨论】:

  • 为什么不使用合适的锁/互斥锁?
  • 为什么使用 HTML 来格式化源代码?没有看到编辑器正上方的格式化相关按钮吗?
  • @phresnel:现在看到了,谢谢

标签: performance scalability atomic memcpy


【解决方案1】:

已经很晚了,但对于其他解决这个问题的人来说,以下内容更容易更快并且对缓存的压力更小。

注意:我将 CAS 更改为 GCC 中相应的 atomic builtin。不需要“volatile”,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 是否能进一步提高效率。

【讨论】:

    【解决方案2】:

    不要使用锁,使用 CriticalSection。锁是重量级的,CriticalSection 非常非常快速(取决于平台,只有几个指令)。您没有指定操作系统,我在此处发布的信息是在 Windows 中体验的,尽管其他操作系统应该类似。

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

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

    【讨论】:

      【解决方案3】:

      你的代码 sn-p 肯定坏了。 node->startFlag

      上有比赛

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

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

      希望对您有所帮助。 亚历克斯。

      【讨论】:

      • 实际上我维护了自己的内存管理器,因此调用内存分配和空闲内存调用的成本很高。所以我不能使用你提到的第三个。你能评论一下我在代码 sn-p 中展示的方法吗?是的,在 startFlag 上有比赛,但这是测试和测试和设置的某种方式,我在此之前检查变量的值以减少一些昂贵的原子操作。如果您建议我的解决方案将如何遇到麻烦,那就太好了。虽然我已经阅读了你所有关于 atomic-spinlock 的帖子。
      • 自旋锁会禁用中断吗?我不这么认为..在那种情况下,我的设计看起来也不错..
      • 好的。假设您的 CompareAndSwap() 是一个原子集和测试函数。那么你需要第一个 if 语句做什么呢?它破坏了整个代码 sn-p。请记住,一旦您使用原子操作访问某个变量,您应该始终使用原子操作访问它。另外,你真的不需要这个 if 语句。
      • 是的 CompareAndSwap 是一个原子操作。我使用该 if 语句来减少一些昂贵的 compareAndSet (或 test-and-set )。但是我将该变量用作 volatile 变量,如果该 if 语句为假,将阻止调用 compare-And-Set ,就像我们在 test-and-test-and-set 中所做的那样( test-and-set 的扩展) .见维基en.wikipedia.org/wiki/Test_and_Test-and-set。整个sn-p怎么样,看起来可行吗?
      【解决方案4】:

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

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

      【讨论】:

      • 如果你想序列化你的线程,你无法逃脱阻塞。通过可扩展性,我考虑的是单个互斥体需要在每个线程中添加的行数。
      • 原子操作只能处理少量数据,通常是一个寄存器。除此之外,如果你想要一个更大的原子操作,你可以在汇编中使用LOCK(不是每个程序员都可以使用)。如果数据较长,普通用户空间程序将使用同步机制。
      • 同意,原子操作是为指针大小的内存位置提供的,但是有像 Double Atomic 这样的方式,通常称为 DCAS 和扩展是 Word 软件事务内存和对象软件事务内存(虽然没有被广泛接受)。所以我一直在寻找原子 memcpy,因为要复制的数据很小(64 字节)
      • 是的,STM 方法避免了锁定。但在这种情况下,仅将它们用于单个 memcpy 是很痛苦的。
      • 那你怎么看?什么可以使这个可扩展?你能对我展示的似乎是无锁的代码发表评论吗?并删除您较早的评论以继续讨论,因为 SO 不允许在此处聊天/讨论(一对一)。有答案吗?
      猜你喜欢
      • 2012-05-24
      • 2011-12-28
      • 2014-05-08
      • 1970-01-01
      • 1970-01-01
      • 2018-07-30
      • 1970-01-01
      • 2011-03-16
      • 1970-01-01
      相关资源
      最近更新 更多