【问题标题】:Make gcc use conditional moves让 gcc 使用条件移动
【发布时间】:2015-08-09 18:56:59
【问题描述】:

是否有 gcc pragma 或我可以用来强制 gcc 在特定代码部分生成无分支指令的东西?

我有一段代码希望 gcc 使用 cmov 指令编译成无分支代码:

int foo(int *a, int n, int x) {
    int i = 0, j = n;

    while (i < n) {
#ifdef PREFETCH
        __builtin_prefetch(a+16*i + 15);
#endif /* PREFETCH */
        j = (x <= a[i]) ? i : j;
        i = (x <= a[i]) ? 2*i + 1 : 2*i + 2;
    }
    return j;
}

确实如此:

morin@soprano$ gcc -O4 -S -c test.c -o -    
    .file   "test.c"
    .text
    .p2align 4,,15
    .globl  foo
    .type   foo, @function
foo:
.LFB0:
    .cfi_startproc
    testl   %esi, %esi
    movl    %esi, %eax
    jle .L2
    xorl    %r8d, %r8d
    jmp .L3
    .p2align 4,,10
    .p2align 3
.L6:
    movl    %ecx, %r8d
.L3:
    movslq  %r8d, %rcx
    movl    (%rdi,%rcx,4), %r9d
    leal    (%r8,%r8), %ecx      # put 2*i in ecx
    leal    1(%rcx), %r10d       # put 2*i+1 in r10d
    addl    $2, %ecx             # put 2*i+2 in ecx
    cmpl    %edx, %r9d
    cmovge  %r10d, %ecx          # put 2*i+1 in ecx if appropriate
    cmovge  %r8d, %eax           # set j = i if appropriate
    cmpl    %esi, %ecx
    jl  .L6
.L2:
    rep ret
    .cfi_endproc
.LFE0:
    .size   foo, .-foo
    .ident  "GCC: (Ubuntu 4.8.2-19ubuntu1) 4.8.2"
    .section    .note.GNU-stack,"",@progbits

(是的,我意识到循环是一个分支,但我说的是循环内的选择运算符。)

不幸的是,当我启用__builtin_prefetch 调用时,gcc 会生成分支代码:

morin@soprano$ gcc -DPREFETCH -O4 -S -c test.c -o -
    .file   "test.c"
    .text
    .p2align 4,,15
    .globl  foo
    .type   foo, @function
foo:
.LFB0:
    .cfi_startproc
    testl   %esi, %esi
    movl    %esi, %eax
    jle .L7
    xorl    %ecx, %ecx
    jmp .L5
    .p2align 4,,10
    .p2align 3
.L3:
    movl    %ecx, %eax           # this is the x <= a[i] branch
    leal    1(%rcx,%rcx), %ecx
    cmpl    %esi, %ecx
    jge .L11
.L5:
    movl    %ecx, %r8d           # this is the main branch
    sall    $4, %r8d             # setup the prefetch
    movslq  %r8d, %r8            # setup the prefetch
    prefetcht0  60(%rdi,%r8,4)   # do the prefetch
    movslq  %ecx, %r8
    cmpl    %edx, (%rdi,%r8,4)   # compare x with a[i]
    jge .L3
    leal    2(%rcx,%rcx), %ecx   # this is the x > a[i] branch
    cmpl    %esi, %ecx
    jl  .L5
.L11:
    rep ret
.L7:
    .p2align 4,,5
    rep ret
    .cfi_endproc
.LFE0:
    .size   foo, .-foo
    .ident  "GCC: (Ubuntu 4.8.2-19ubuntu1) 4.8.2"
    .section    .note.GNU-stack,"",@progbits

我试过在这个函数上使用__attribute__((optimize("if-conversion2"))),但是没有效果。

我如此关心的原因是我手工编辑了编译器生成的无分支代码(来自第一个示例)以包含 prefetcht0 指令,它的运行速度比 gcc 生成的两个版本快得多。

【问题讨论】:

  • 你编译的优化级别是多少?因为当我编写普通代码并使用 -O3 或 -Ofast 时,我很难击败编译器
  • 我猜你可以提示 gcc 条件是真还是假的概率。看起来您的 gcc 非常确信该分支要么大部分为真,要么大部分为假,因此运行时预测比预测效果更好?
  • @GradyPlayer:这是用 -O4 编译的,在第一个例子中它愉快地使用了 cmov 操作。
  • @user3528438 :在这种情况下,gcc 是完全错误的。每个分支在每一步都有 50% 的机会被采用。
  • 显然,这并不是特定于预取的。只需添加例如asm (""); 代替 __builtin_prefetch 具有相同的效果。

标签: c gcc optimization x86


【解决方案1】:

如果您真的依赖这种优化级别,则必须编写自己的汇编存根。

原因是即使在代码的其他地方进行修改也可能会更改编译器(非 gcc 特定)发出的代码。此外,不同版本的 gcc、不同的选项(例如 -fomit-frame-pointer)可以显着改变代码。

只有在必要时才应该这样做。其他影响可能会产生更大的影响,例如缓存配置、内存分配(DRAM-page/bank)、与并发运行程序相比的执行顺序、CPU 关联等等。首先玩编译器优化。您可以在docs 中找到命令行选项(您没有发布使用的版本,因此没有更具体)。

一个(严重的)替代方案是使用 clang/llvm。或者只是帮助 gcc 团队改进他们的优化器。你不会是第一个。另请注意,gcc 在上一版本中专门针对 ARM 进行了大量改进。

【讨论】:

  • 很遗憾,这是不可接受的。上面的代码只是一个最小的工作示例。实际上,这是一个模板化的 C++ 方法,其中“int* a”是“T* a”,“int x”是“T x”。我不能为每个可能的类型 T 编写汇编存根。这段代码是研究数组布局对搜索的影响的项目的一部分。预取指令非常具体的原因是:包含预取地址的缓存行将在预取指令发出后恰好四次迭代被访问。
  • @PatMorin:没有理由责怪(投反对票?-态度不好)信使发出不愉快的消息。如果您帮助 gcc 团队增强优化算法,他们很可能会很高兴。或者,您可以尝试 clang/llvm;在这里可能会更好,或者 - 至少 - 应该更容易使用您自己的优化器进行扩展。另请注意,您标记了问题 C 并且 C 中没有模板,因此您不妨责怪自己提供了错误的信息。
  • 很公平。对不起。我只是想强调写汇编不是问题的答案,而是跳了枪。我会删除我的反对票,但除非您更改答案,否则我似乎不允许这样做。
  • @PatMorin:接受。只是想帮忙。 (这本身并不是为了投反对票,但我只是认为这不公平)。
  • 确实,clang 在这种情况下做了我想要的(但在另一种情况下 g++ 处理得很好。)
【解决方案2】:

看起来 gcc 可能无法为循环条件和后置条件中使用的变量生成无分支代码,以及在伪函数内部调用中保持临时寄存器活动的约束。

有些可疑之处,使用 -funroll-all-loops 和 -fguess-branch-probability 时,您的函数生成的代码不同。我生成了许多返回指令。它闻起来像是 gcc 中的一个小错误,围绕着编译器的 rtl pass,或者是代码块的简化。

以下代码在这两种情况下都是无分支的。这将是向 GCC 提交错误的一个很好的理由。在 -O3 级别,GCC 应该始终生成相同的代码。

int foo( int *a,  int n,  int x) {
     int c, i = 0, j = n;

    while (i < n) {
#ifdef PREFETCH
        __builtin_prefetch(a+16*i + 15);
#endif /* PREFETCH */
        c = (x > a[i]);
        j = c ? j : i;
        i = 2*i + 1 + c;
    }
    return j;
}

生成这个

        .cfi_startproc
        testl   %esi, %esi
        movl    %esi, %eax
        jle     .L4
        xorl    %ecx, %ecx
        .p2align 4,,10
        .p2align 3
.L3:
        movslq  %ecx, %r8
        cmpl    %edx, (%rdi,%r8,4)
        setl    %r8b
        cmovge  %ecx, %eax
        movzbl  %r8b, %r8d
        leal    1(%r8,%rcx,2), %ecx
        cmpl    %ecx, %esi
        jg      .L3
.L4:
        rep ret
        .cfi_endproc

还有这个

        .cfi_startproc
        testl   %esi, %esi
        movl    %esi, %eax
        jle     .L5
        xorl    %ecx, %ecx
        .p2align 4,,10
        .p2align 3
.L4:
        movl    %ecx, %r8d
        sall    $4, %r8d
        movslq  %r8d, %r8
        prefetcht0      60(%rdi,%r8,4)
        movslq  %ecx, %r8
        cmpl    %edx, (%rdi,%r8,4)
        setl    %r8b
        testb   %r8b, %r8b
        movzbl  %r8b, %r9d
        cmove   %ecx, %eax
        leal    1(%r9,%rcx,2), %ecx
        cmpl    %ecx, %esi
        jg      .L4
.L5:
        rep ret
        .cfi_endproc

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2019-09-18
    • 1970-01-01
    • 2021-01-12
    • 1970-01-01
    • 2023-03-11
    相关资源
    最近更新 更多