首页 > 解决方案 > 为什么 Clang 不将这些连续和相邻的调用合并到 memcpy 或 memset?

问题描述

当使用 Clang 10.0.0 和当前的 Clang-trunk 编译它时,它似乎缺少一些非常明显的优化机会:

struct A {
    int x[16] {0}; // Everything zero init by default
    int y[16] {0};
    int z[16] {0};
};

int foo(A* a, A* b); // dummy function in external translation unit
                     // to block any constant folding and unused variable
                     // elimination, etc.

int func() {

    A a[1024],b[1024]; // clang calls memset for each of a and b
                       // could be optimized to single call

    foo(a,b);

    for( int i = 0 ; i < 1024 ; i++ ) {
        a[i] = b[i]; // clang calls memcpy here 1024 times with
                     // consecutive addresses, could be optimized
                     // to single call.
    }

    return foo(a,b);
}

Clang 在 -O3 处为 x64 生成的输出如下: https ://godbolt.org/z/taKi4G

func():                               # @func()
        push    r15
        push    r14
        push    rbx
        sub     rsp, 393216
        lea     r14, [rsp + 196608]
        xor     ebx, ebx
        mov     edx, 196608
        mov     rdi, r14
        xor     esi, esi
        call    memset
        mov     r15, rsp
        mov     edx, 196608
        mov     rdi, r15
        xor     esi, esi
        call    memset
        mov     rdi, r14
        mov     rsi, r15
        call    foo(A*, A*)
.LBB0_1:                                # =>This Inner Loop Header: Depth=1
        lea     rdi, [rsp + rbx]
        add     rdi, 196608
        lea     rsi, [rsp + rbx]
        mov     edx, 192
        call    memcpy
        lea     rdi, [rsp + rbx]
        add     rdi, 196800
        lea     rsi, [rsp + rbx]
        add     rsi, 192
        mov     edx, 192
        call    memcpy
        add     rbx, 384
        cmp     rbx, 196608
        jne     .LBB0_1
        lea     rdi, [rsp + 196608]
        mov     rsi, rsp
        call    foo(A*, A*)
        add     rsp, 393216
        pop     rbx
        pop     r14
        pop     r15
        ret

我认为对 memset 的两个调用可以合并为一个 memset,对 memcpy 的 1024 个调用可以合并,留下一个 memset 和一个 memcpy。memset 的改进可能是微不足道的,但我很确定一次调用 memcpy 会明显更快。

我试图说服某人他们不应该在他们的代码中使用 mempcy 和 memset 来复制和归零 POD 结构的初始化数组,而应该只使用构造函数并让编译器处理它。当我实际查看 clang 生成的内容时,我的论点有些分崩离析。

进行这种合并是无效的还是clang只是错过了一个技巧?

编辑:我检查了 GCC 10,它似乎按照我的预期做了很大的 mempcy,但仍然没有合并 memset。

标签: c++clangcompiler-optimizationmemcpymemset

解决方案


推荐阅读