2

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

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

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

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

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

通过在这里和那里插入 gettimeofday,我确定方法 #2 中所有增加的延迟都花在循环中,反复检查下一个指针是否为非空。这让我很困惑;就好像一个流程中的更改需要更长的时间才能使用第二种方法“发布”到第二个流程。我假设正在进行某种我不明白的缓存交互。这是怎么回事?

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

更新 2: node->next_ 是不稳定的。

4

3 回答 3

2

由于听起来读写是在不同的 CPU 上发生的,也许内存屏障会有所帮助?您的写入可能不会在您期望的时候发生。

于 2010-03-26T17:13:16.043 回答
0

您正在 #2 中进行自旋锁,这通常不是一个好主意,并且正在消耗周期。

于 2010-03-26T15:35:45.787 回答
0

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

使用Boost.Thread,它看起来像这样:

while(node->next_ == NULL) {
    boost::this_thread::yield( );
}
于 2010-03-26T15:37:48.563 回答