为什么一个循环需要更长的时间来检测共享内存更新而不是另一个循环?

时间:2010-03-26 15:26:20

标签: c++ multithreading shared-memory latency spinlock

我编写了一个写入共享内存的“服务器”程序,以及一个从内存中读取的客户端程序。服务器具有可以写入的不同“通道”,它们只是不同的链接列表,它也是附加项目。客户端对某些链接列表感兴趣,并希望读取添加到这些列表中的每个节点,并尽可能减少延迟。

我有两种方法供客户使用:

  1. 对于每个链接列表,客户端保留一个“书签”指针,以保持其在链接列表中的位置。它循环链接列表,一遍又一遍地遍历所有链接(它永远循环),如果可能的话,每次将每个书签向前移动一个节点。它是否可以由节点的“下一个”成员的值确定。如果它是非null,则跳转到下一个节点是安全的(服务器将其从原子级的null切换为非null)。这种方法运行正常,但如果有很多列表需要迭代,并且只有少数列表正在接收更新,则延迟会变得很糟糕。

  2. 服务器为每个列表提供唯一的ID。每次服务器将项目附加到列表时,它还会将列表的ID号附加到主“更新列表”。客户端只将一个书签,一个书签保留在更新列表中。它无休止地检查书签的下一个指针是否为非空(while(node->next_ == NULL) {}),如果是,则向前移动,读取给定的ID,然后处理具有该ID的链接列表上的新节点。理论上,这应该更好地处理大量列表,因为客户端不必每次都迭代它们。

  3. 当我对这两种方法的延迟进行基准测试时(使用gettimeofday),令我惊讶的是#2非常糟糕。对于少量链接列表,第一种方法通常不到20us的延迟。第二种方法是低延迟的小稚贝,但通常在4,000-7,000us之间!

    通过在这里和那里插入gettimeofday,我已经确定方法#2中所有增加的延迟都是在循环中反复检查下一个指针是否为非空。这让我感到困惑;就好像一个过程中的变化需要更长的时间来用第二种方法“发布”到第二个过程。我假设存在某种缓存交互,我不明白。发生了什么事?

    更新:最初,方法#2使用了一个条件变量,因此如果node->next_ == NULL它会在条件上等待,并且服务器会在每次发出更新时通知该条件。延迟是相同的,并试图弄清楚为什么我将代码减少到上面的方法。我在多核机器上运行,因此一个进程自旋锁定不应该影响另一个。

    更新2:node-> next_是易失性的。

3 个答案:

答案 0 :(得分:2)

由于听起来读取和写入发生在不同的CPU上,或许memory barrier会有帮助吗?您的写作可能不会在您预期的时候发生。

答案 1 :(得分:0)

你在#2中做Spin Lock,这通常不是一个好主意,并且正在咀嚼周期。

答案 2 :(得分:0)

您是否尝试在第二种方法中每次失败的轮询尝试后添加yield?只是一个猜测,但它可能会减少功率循环。

使用Boost.Thread,这将是这样的:

while(node->next_ == NULL) {
    boost::this_thread::yield( );
}