【发布时间】:2010-03-26 15:26:20
【问题描述】:
我编写了一个写入共享内存的“服务器”程序,以及一个从内存读取的客户端程序。服务器有不同的“通道”可以写入,它们只是不同的链接列表,它也可以附加项目。客户端对某些链表感兴趣,并希望读取添加到这些链表中的每个节点,并尽可能降低延迟。
我有两种方法供客户使用:
对于每个链表,客户端保留一个“书签”指针以保持其在链表中的位置。它循环循环链表,一遍又一遍地遍历所有链表(它永远循环),如果可以的话,每次将每个书签向前移动一个节点。是否可以由节点的“下一个”成员的值确定。如果它是非空的,那么跳转到下一个节点是安全的(服务器自动将它从空切换到非空)。这种方法工作正常,但如果有很多列表要迭代,而其中只有少数正在接收更新,那么延迟就会变差。
服务器为每个列表提供一个唯一的 ID。每次服务器将项目附加到列表时,它也会将列表的 ID 号附加到主“更新列表”。客户端只保留一个书签,一个书签到更新列表中。它无休止地检查书签的下一个指针是否为非空(
while(node->next_ == NULL) {}),如果是,则继续读取给定的 ID,然后处理链表上具有该 ID 的新节点。从理论上讲,这应该可以更好地处理大量列表,因为客户端不必每次都遍历所有列表。
当我对这两种方法的延迟进行基准测试时(使用 gettimeofday),令我惊讶的是 #2 非常糟糕。对于少量链表,第一种方法的延迟通常低于 20us。第二种方法会有一些低延迟,但通常在 4,000-7,000us 之间!
通过在这里和那里插入 gettimeofday,我确定方法 #2 中所有增加的延迟都花在循环中,反复检查下一个指针是否为非空。这让我很困惑;就好像一个流程中的更改需要更长的时间才能使用第二种方法“发布”到第二个流程。我假设正在进行某种我不明白的缓存交互。怎么回事?
更新:最初,方法#2 使用条件变量,因此如果node->next_ == NULL 它将等待该条件,并且服务器将在每次发布更新时通知该条件。延迟是相同的,并且试图弄清楚为什么我将代码减少到上述方法。我在多核机器上运行,所以一个进程自旋锁不应该影响另一个。
更新 2:node->next_ 是不稳定的。
【问题讨论】:
-
这里真的需要自旋锁吗?为什么不使用平台提供的事件对象之类的东西,以便客户端可以很好地等待(不占用 CPU、缓存或内存访问资源),直到服务器发出新数据的信号?
-
我先试了一个条件变量,见更新。
-
你在阅读
node->next_原子吗? -
更新后 - 在#2 中,客户端和服务器似乎只通过一个共享的内存位(书签节点)进行通信,因此该资源可能存在更多争用。你能测算客户端读取node->next需要多长时间,服务器写入需要多长时间?
-
读取始终是瞬时的(0 或 1 微秒)。我会检查写...
标签: c++ multithreading shared-memory latency spinlock