提问者:小点点

C优化:条件存储以避免弄脏缓存行


在libuv源代码中,我找到了这段代码:

  /* The if statement lets the compiler compile it to a conditional store.
   * Avoids dirtying a cache line.
   */
  if (loop->stop_flag != 0)
    loop->stop_flag = 0;

有人能解释一下吗?

缓存线到底是什么?

此外,我猜条件存储是一些汇编指令,它检查某些内容,如果成功,则写入一些值。对吗?

这样的构造什么时候有意义?我想并不总是,因为否则编译器将始终使用条件存储,对吗?


共2个答案

匿名用户

缓存被组织成快速内存块,由于历史原因,这些块被称为行。当您写入缓存行时,它被标记为“脏”,这意味着在缓存控制器硬件中设置了一个位,表示该行需要复制到其他级别的缓存和/或主存储器,然后系统的其他部分才能访问它。

一般来说,内存层次结构的每个级别:寄存器、L1、L2、L3…缓存、主存储器和交换空间都有相同信息的不同副本。确保系统的不同部分(处理器、DMA、视频子系统等)看到相同的值,即使一个或多个副本可能发生了变化,这称为一致性问题。

一般的解决方案是暂停以将更新的值复制到层次结构的不同级别。这称为刷新。

刷新可能花费10到——在最坏的情况下,当它导致页面错误时——可能需要数百万个处理器周期。

由于这种高成本,硬件设计人员竭尽全力减少对刷新的需求。在这里,程序员也承担了这项事业。

评论说“如果缓存已经在标志中包含零,我们不要在零上写零,因为这会将缓存行标记为脏,这可能会导致不必要的刷新。”

“条件存储”是一个有点晦涩的术语。它只是指普通存储周围的零跳转,这是编译器将从if语句生成的代码。在X86中,它看起来像:

    ;; assume edi holds value of pointer 'loop'
    ;; and flag is a constant offset for the 'stop_flag' field.
    cmp dword ptr [edi, flag], 0
    jz no_store
    mov [edi, flag], 0
no_store:
   ... code continues

缺少if语句时,您将只有最后一条mov指令。

NB一位评论者指出,在重要的处理器架构上确实存在单个“条件移动/存储”指令。我还没有看到gcc产生一个。

这是否是一个有价值的优化是非常有争议的。条件有自己冲洗指令管道的风险(另一种冲洗)。在没有明确证据表明需要的情况下,永远不要为了速度而牺牲清晰度。

匿名用户

“to cache”的意思是隐藏一些东西。Cache在计算中的作用是隐藏到主存的距离,通过尽可能抢占主存访问。

这只有在您之前使用过数据时才有效,您还没有将其从缓存中推出,并且在您之前没有其他人将其带走。任何其他参与者(其他CPU,IO-Bus,…)必须能够获取当前值并对其进行更改,即使您已将其缓存。这项任务是使用缓存一致性协议完成的。更高的一致性意味着更高的成本。

你的代码试图做的是让编译器发出一个条件移动,所以CPU检查0,只有在不是0的情况下才写入。英特尔/AMD信息系统和许多其他系统中有一整套条件移动指令。

所以现在,一步一步:

  • 测试为0:如果你的CPU没有测试数据的副本,它必须要求一个。这比以前糟糕得多。让我们希望你不要访问主内存。
  • 准备写一个值:
    • 你拥有数据:太好了,你已经完成了。
    • 您不拥有数据:Cache打电话给它的兄弟和更高层,通知他们它现在拥有这块数据。没有人可以保留副本。

    那么,值得吗?看情况。

    旁白:如果你可以使用条件跳转和存储来合成它们,为什么要提供条件存储指令?优点是使用更少的指令,并且没有刷新指令管道的风险(部分按照指令执行)。更新:看起来它们不能在x86/x86_64上从寄存器/即时移动到内存。

相关问题