【问题标题】:C optimization: conditional store to avoid dirtying a cache lineC 优化:条件存储以避免弄脏缓存行
【发布时间】:2014-05-08 03:52:39
【问题描述】:

libuv source,我找到了这段代码:

  /* 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;

有人能解释一下吗?

究竟什么是缓存行?

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

这种结构什么时候有意义?我想并不总是这样,否则编译器只会使用条件存储,对吧?

【问题讨论】:

    标签: c libuv


    【解决方案1】:

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

    一般来说,内存层次结构的每一级:寄存器、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 生产一个。

    这是否值得优化值得商榷。条件语句有自己的刷新指令流水线的风险(一种不同的刷新)。永远不要在没有明确证据表明需要的情况下牺牲清晰度来换取速度。

    【讨论】:

    • 所以,关于我的最后一个问题:听起来如果值的地址在主内存中,那么使用这样的条件存储几乎总是更好。为什么编译器不自动这样做?
    • 因为它只是一个优化,如果你能保证值在(未修改的)缓存中。否则这是一种悲观,最坏的情况是强制访问主存。
    • 编译器优化如果它总是正确的。测试然后分配需要一个额外的测试和一个条件跳转。它将花费 2 个(至少)CPU 周期,并且是宝贵的管道猜测之一。编译器无法猜测它是否值得缓存的速度。
    • 关于条件存储:这是许多架构的指令,而不是按顺序排列的几条指令。阅读您的汇编手册。请特别注意CMOVNZ
    • @Deduplicator CMOVCC 只是用于注册指令的内存,因此在这种情况下不适用。
    【解决方案2】:

    “缓存”意味着隐藏某些东西。 Cache在计算中的作用是隐藏与主存的距离,尽可能抢占主存访问。

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

    您的代码试图做的是使编译器发出条件移动,因此 CPU 检查 0,如果不是 0,则仅写入。在 Intel/AMD IS 和许多其他指令中有一整套条件移动指令.

    现在,一步一步来:

    • Test for 0:如果您的 CPU 没有测试数据的副本,它必须要求一个。这比以前差多了。希望您没有碰到主内存。
    • 准备写入值:
      • 您拥有数据:太好了,您已经完成了。
      • 您不拥有数据:缓存调用它的兄弟和更高层通知他们它现在拥有这块数据。没有其他人可以保留副本。
    • 写一个值:缓存保存更改并将缓存线(缓存的最低粒度)标记为脏,需要写回。

    那么,值得吗?视情况而定。

    旁白:如果您可以使用条件跳转和存储来综合它们,为什么还要提供条件存储指令?优点是使用较少的指令并且没有刷新指令流水线的风险(部分在指令之后执行)。更新:看起来他们无法在 x86/x86_64 上从寄存器/立即数移动到内存。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2014-03-25
      • 2018-10-20
      • 1970-01-01
      • 2012-07-09
      • 2023-03-06
      • 2020-07-23
      • 2021-04-28
      相关资源
      最近更新 更多