【问题标题】:clang: Is there a way to specify low level instructions used in c11 atomic operations?clang:有没有办法指定 c11 原子操作中使用的低级指令?
【发布时间】:2019-10-18 08:11:33
【问题描述】:

我可以告诉 clang “使用 xxx 指令”或“不要使用 yyy 指令”进行内置或 C11 原子操作(假设有替代方案)?

[编辑] 可以重现性能回归的微基准:a naive rwlock

这一切都始于最近升级到 clang 9.0。我的程序中有一些性能下降,所以我尝试检查生成的指令。令人惊讶的是,clang 在用于原子函数的指令方面发生了很大变化(c11 在 x86_64 上的 atomic_fetch_{add,sub})。

使用 clang 8,它都会生成 lock addllock subl。但是clang 9切换到lock incllock decl用于+1和-1,lock xadd用于其他数字(至少用于加法。我没有检查是否有xsub,或者subl仍然存在)。 inc/xadd/add 指令通常具有相同的速度,但在某些情况下,我观​​察到lock addl 的效率更高。无论如何,我不打算讨论一个 insn 是否可以比另一个更快。既然clang 8用过,最好有办法继续用。

我尝试使用内联汇编来强制使用lock addl,但由于内联汇编阻止了一些优化,因此很难充分利用其效率。理想的解决方案是告诉编译器以旧方式进行。

我没有在这里发布任何代码,因为问题不在于提高性能。但我想将代码分享给任何愿意验证性能问题的人。

[更新] 我在 Broadwell 和 Skylake 上都对其进行了测试。性能回归仅在某些工作负载下可见。我不确定它是否真的与指令或 clang8/9 进行优化的方式有关。

【问题讨论】:

  • xsub 不存在,所以 clang 不能使用它,它使用带有负值的 lock xadd

标签: c x86 clang atomic compiler-optimization


【解决方案1】:

您可以在 clang 的错误跟踪器上报告性能回归,特别是如果您可以使用 GCC9 生成一个较慢的 MCVE / [mcve],原因与您的主代码相同。

如果不使用内联 asm 完全自己完成,通常无法强制编译器的指令选择选择。

但调整选项可能是相关的,例如-march=native 调整您的硬件。 (-march=haswell-march=native on 一个 haswell,例如设置 -mtune=haswell 以及启用您拥有的所有 ISA 扩展。)

例如gcc 或 clang 通常有时会避免使用inc,但在为没有问题的特定 CPU 进行编译时使用它。 (不谈lock 版本,只谈inc reg)。

这里的情况似乎是这样:使用-march=skylake 让clang9.0 使用lock inc 而不是lock add [mem], 1,但在Godbolt 上却没有使用clang8.0。(只有-O3,clang9.0仍然使用lock add/sub)

inc reg 在现代 x86 上很好(Silvermont / KNL 除外),但inc mem(无锁)在 Intel CPU 上需要额外的微指令:没有加载+添加微指令的微融合,只有存储部分(https://agner.org/optimize/https://uops.info/)。如果lock inclock add 相比更糟,或者您看到其他效果,IDK。 INC instruction vs ADD 1: Does it matter?

根据https://uops.info/lock add m32, imm8 与 Skylake 上的lock inc m32相同 uop 计数 (8)、延迟和吞吐量。 在 Haswell 上少了一个-end uop,就像没有锁前缀的区别。但这不太可能影响吞吐量。我没有检查其他的uarches,你也没有说你有什么CPU。

我不会真正推荐-march=skylake -mtune=generic:它可能会解决这个代码生成问题,但可能会导致您的其余代码做出更糟糕的调整决策。除了它甚至不起作用之外,我想 clang 在处理拱形和调整选项的方式上与 GCC 不同。我想您可以完全避免使用 March 选项并将 -mtune 保留为默认值,而只需启用 -mavx2 -mfma -mpopcnt -mbmi -mbmi2 -maes -mcx16 和您的 CPU 具有的任何其他相关 ISA 扩展。


lock xadd 用于其他数字(至少用于加法...

您确定您仍然为新编译器启用优化吗?

--*patomic_fetch_add(p, -2)的结果未被使用时,clang 9.0仍然使用lock declock sub。如果我禁用优化,我只能让clang使用lock xadd,使周围的代码变成完全垃圾。

或者启用优化,通过返回结果。 IDK,也许在更复杂的函数中,clang9.0 改变了一些东西,这意味着它没有在你的代码中找到相同的优化,并使用lock xadd 将旧值放入寄存器。如果它决定不积极内联,可能会将其返回给忽略它的调用者。

lock xadd 肯定比lock addlock sub 慢,但clang 不会使用它,除非它必须(或者如果你禁用优化)。

clang8.0 -O3 -march=skylake 与 clang9.0 -O3 -march=skylake 的 asm 输出(不包括 ret)(Godbolt)

#include <stdatomic.h>

void incmem(int *p) { ++*p; }
    clang8:     addl   $1, (%rdi)       clang9:  incl    (%rdi)

void atomic_inc(_Atomic int *p) { ++*p; }
    clang8: lock addl  $1, (%rdi)       clang9: lock incl (%rdi)

void atomic_dec(_Atomic int *p) { --*p; }
    clang8: lock subl  $1, (%rdi)       clang9: lock decl (%rdi)

void atomic_dec2(_Atomic int *p) {
    atomic_fetch_add(p, -2);
}
    clang8: lock addl  $-2, (%rdi)      clang9: lock addl $-2, (%rdi)


// returns the result
int fetch_dec(_Atomic int *p) { return --*p; }
    clang8:                                    clang9:
        movl  $-1, %eax                            movl  $-1, %eax
        lock xaddl   %eax, (%rdi)                  lock  xaddl %eax, (%rdi)
        addl  $-1, %eax                            decl    %eax
        retq

禁用优化后,clang 8 和 9 与 -O0 -march=skylake 生成完全相同的代码:

# both clang8 and 9 with  -O0 -march=skylake
atomic_dec2:
        pushq   %rbp
        movq    %rsp, %rbp
        movq    %rdi, -8(%rbp)
        movq    -8(%rbp), %rax
        movl    $-2, -12(%rbp)
        movl    -12(%rbp), %ecx
        lock    xaddl   %ecx, (%rax)      # even though result is unused
        movl    %ecx, -16(%rbp)
        popq    %rbp
        retq

【讨论】:

  • 欣赏!我会尽快将它报告给 clang/llvm 邮件列表。
  • @Nybble:如果您有 MCVE,请在 bugs.llvm.org 上将其提交为错过优化(也称为性能错误)。
  • +1 变成 lock inc,+n 变成 lock xadd。我不认为这是关于优化。 -O0 绝不应该有意产生较慢的代码。 (我使用的是 -O3 -march=native -flto)
  • @Nybble:就像我在回答中所说的那样,我无法在 clang9 或 Godbolt 上将 fetch_add 编译为 lock xadd 复制,除非使用了结果,所以它别无选择。你到底用的是什么clang版本?
  • @Nybble: re: -O0 永远不应该故意生成较慢的代码:这确实是真的,它故意快速编译,花费很少的 CPU 时间优化生成的 asm。我的fetch_add-O0 示例显示我们得到lock xadd,因为优化没有注意到结果未使用,和/或没有尝试窥视优化到lock inclock add。缺少窥视孔优化正是我们应该期待的 -O0 的那种事情。 (以及由于不进行寄存器分配而导致的大量减速,而是重新加载+溢出每个语句以进行调试)
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2020-06-23
  • 2014-12-30
  • 1970-01-01
  • 2013-07-02
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多