代码之家  ›  专栏  ›  技术社区  ›  Hongli

在x86上哪个写屏障更好:lock+addl或xchgl?

  •  22
  • Hongli  · 技术社区  · 13 年前

    Linux内核使用 lock; addl $0,0(%%esp) 当RE2库使用 xchgl (%0),%0 作为写屏障。有什么区别,哪一个更好?

    x86是否也需要阅读屏障说明?RE2将其读屏障函数定义为x86上的no op,而Linux将其定义为 lfence 或者没有OP,取决于SSE2是否可用。什么时候是 篱笆 必修的?

    5 回复  |  直到 13 年前
        1
  •  8
  •   Peter Cordes    5 年前

    锁定;添加$0,0(%%esp) “如果我们在(%%esp)地址测试锁变量的0状态,则速度更快。因为我们将0值添加到锁变量,如果地址(%%esp)处变量的锁值为0,则零标志设置为1。


    篱笆 从Intel数据表:

    对执行序列化操作 所有从内存加载指令 是在LFENCE之前发布的 说明。这个系列化 运行保证每台负荷 在程序中位于前面的指令 命令LFENCE指令是 任何加载前全局可见 遵循原则的指示 指令是全局可见的。

    ( 编者按: mfence 或A lock ED操作是(在存储之后)实现顺序一致性的唯一有用屏障。 . lfence 按存储缓冲区阻止存储负载重新排序。)


    例如:“mov”之类的内存写入指令是原子的(它们不需要锁前缀),如果它们正确对齐。但此指令通常在CPU缓存中执行,此时对于所有其他线程来说,它将不全局可见,因为必须首先执行内存隔离,以使此线程等待,直到其他线程看到以前的存储。


    所以这两个指令的主要区别是 XCHGL 指令对条件标志没有任何影响。当然,我们可以用 锁定cmpxchg 但这仍然比 加锁$0 说明。

        2
  •  10
  •   Fabian Giesen    13 年前

    引用IA32手册(第3a卷,第8.2章:内存排序):

    在定义为回写可缓存的单处理器内存区域系统中,内存排序模型遵循以下原则[..]

    • 读取不与其他读取重新排序
    • 写操作不会用旧的读操作重新排序
    • 除了
      • 使用执行的写入 CLFLUSH 指令
      • 使用非暂时移动指令执行的流存储(写入)([此处的指令列表])
      • 串操作(见第8.2.4.1节)
    • 读操作可以用不同位置的旧写操作重新排序,但不能用相同位置的旧写操作重新排序。
    • 读或写不能用I/O指令、锁定指令或序列化指令重新排序
    • 读取无法通过 LFENCE MFENCE 说明
    • 写入无法通过 SFENCE 麦克韦尔 说明

    注意:上面的“在单处理器系统中”有些误导。每个(逻辑)处理器都有相同的规则;然后手册将继续描述多个处理器之间的额外排序规则。关于这个问题,唯一的一点是

    • 锁定的指令有一个总的顺序。

    简而言之,只要你写回内存(只要你不是驱动程序或图形程序员,这就是你所能看到的所有内存),大多数x86指令几乎是顺序一致的-唯一可以重新排序的x86 CPU可以执行的是以后重新排序(独立的)读取以执行在写之前。写障碍的主要原因是它们有一个 lock 前缀(隐式或显式),它禁止所有重新排序,并确保多处理器系统中的所有处理器以相同的顺序看到操作。

    而且,在写回内存中,读取永远不会被重新排序,因此不需要设置读取障碍。最近的x86处理器对于流存储和写入组合内存(通常用于映射的图形内存)具有较弱的内存一致性模型。在那里各种各样的 fence 指令开始发挥作用;它们对于任何其他内存类型都不是必需的,但是Linux内核中的一些驱动程序确实处理写入组合内存,因此它们只是以这种方式定义了它们的读取障碍。每种存储器类型的订购型号列表见IA-32手册第3A卷第11.3.1节。短版本:写通、写回和写保护允许推测性读取(遵循上述规则),不可缓存和强不可缓存内存有很强的排序保证(没有处理器重新排序,立即执行读/写,用于MMIO)和写组合莫里的命令很弱(也就是说,放松的命令规则需要围栏)。

        3
  •  7
  •   Peter Cordes    6 年前

    lock addl $0, (%esp) 是替代 mfence 不是 lfence .

    用例是当您需要阻止StoreLoad重新排序(x86的强内存模型所允许的唯一类型)时,但是您不需要对共享变量执行原子的RMW操作。 https://preshing.com/20120515/memory-reordering-caught-in-the-act/

    例如,假设对齐 std::atomic<int> a,b :

    movl   $1, a             a = 1;    Atomic for aligned a
    # barrier needed here
    movl   b, %eax           tmp = b;  Atomic for aligned b
    

    您的选择是:

    • 按顺序存储 xchg ,例如 mov $1, %eax / xchg %eax, a 所以你不需要一个单独的屏障,它是商店的一部分。我认为这是大多数现代硬件上最有效的选择;除了GCC使用之外,C++ 11编译器也是最有效的。 交换 对于顺序存储。
    • 使用 使用全栅障 作为屏障。(GCC使用 mov + 使用全栅障 对于顺序存储)。
    • 使用 锁定addl$0,(%esp) 作为屏障。任何 lock 教育教学是一个全面的障碍。 Does lock xchg have the same behavior as mfence?

      (或者到其他位置,但是堆栈在L1D中几乎总是私有的和热的,所以它是一个比较好的候选者。但是,这可能会使用堆栈底部的数据为某些内容创建依赖链。)

    你只能用 交换 因为它无条件地用一个不依赖于旧值的值写入内存位置,所以将其折叠到存储中作为一个屏障。

    如果可能,使用 交换 对于seq cst存储可能是最好的,即使它也从共享位置读取。 使用全栅障 在最近的Intel CPU上慢于预期( Are loads and stores the only instructions that gets reordered? )也以同样的方式阻止独立非内存指令的无序执行。 篱笆 做。

    它甚至值得使用 lock addl $0, (%esp)/(%rsp) 而不是 使用全栅障 即使在 使用全栅障 有,但我没有尝试过缺点。使用 -64(%rsp) 或者一些事情可能会使它不太可能延长对某个热门事物(本地或返回地址)的数据依赖性,但这会使valgrind等工具不高兴。


    篱笆 对于内存排序永远不会有用,除非您正在使用movntdqa加载从视频RAM(或其他wc弱排序区域)读取。

    序列化无序执行(但不是存储缓冲区)对于停止存储负载重新排序(x86的强内存模型只允许正常的WB(写回)内存区域)没有帮助。

    的实际用例 篱笆 用于阻止不按顺序执行 rdtsc 对于非常短的代码块计时,或者通过条件分支或间接分支阻止推测来进行幽灵缓解。

    另请参见 When should I use _mm_sfence _mm_lfence and _mm_mfence (我的答案和@beeonrope的答案)了解更多原因 篱笆 不是有用的,以及何时使用每个屏障指令。(或在我的,C++内部的,当在C++编程而不是ASM编程时)。

        4
  •  6
  •   Ross Ridge    9 年前

    除了其他答案,热点开发人员发现 lock; addl $0,0(%%esp) 在某些处理器上,零偏移可能不是最佳的 introduce false data dependencies ;相关 jdk bug .

    在某些情况下,使用不同偏移量触摸堆栈位置可以提高性能。

        5
  •  2
  •   Ben Jackson    13 年前

    的重要部分 lock; addl xchgl lock 前缀。它暗示着 XCHGL . 这两者真的没有区别。我将看看它们是如何组装的,并选择一个更短的(以字节为单位),因为对于x86上的等效操作来说,这通常更快(因此类似于 xorl eax,eax )

    SSE2的存在可能只是真实条件的一个代理,它最终是 cpuid . 可能结果表明,SSE2意味着 lfence 启动时检查/缓存了SSE2的可用性。 篱笆 在可用时是必需的。