代码之家  ›  专栏  ›  技术社区  ›  Joseph Garvin

为什么一个循环检测共享内存更新要比另一个循环花费更长的时间?

  •  2
  • Joseph Garvin  · 技术社区  · 14 年前

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

    我为客户提供了两种方法:

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

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

    当我对两种方法的延迟进行基准测试时(使用gettimeofday),令我惊讶的是2是可怕的。对于少量的链表,第一种方法的延迟通常不到20us。第二种方法有少量的低延迟,但通常在4000-7000us之间!

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

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

    更新2:node->next_u不稳定。

    3 回复  |  直到 14 年前
        1
  •  2
  •   zdan    14 年前

    因为听起来读和写是在不同的CPU上进行的,所以 memory barrier 会有帮助吗?你的写作可能不会在你期望的时候发生。

        2
  •  0
  •   Joris Timmermans    14 年前

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

        3
  •  0
  •   Björn Pollex    14 年前

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

    Boost.Thread 这看起来像这样:

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