Почему Clang не объединяет эти последовательные и смежные вызовы memcpy или memset? - PullRequest
0 голосов
/ 26 мая 2020

При компиляции с 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 для x64 при -O3, выглядит следующим образом: 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, а 1024 вызова memcpy можно объединить, оставив один memset и один memcpy. Улучшение memset может быть незначительным, но я почти уверен, что один вызов memcpy будет значительно быстрее.

Я пытался убедить кого-то, что они не должны использовать mempcy и memset в своем коде для копирования и обнуления массивов инициализации структур POD, и должны просто использовать конструкторы и позволить компилятору обрабатывать это. Мой аргумент несколько развалился, когда я на самом деле посмотрел, что сгенерировало clang.

Это слияние как-то недействительно, или clang просто упускает трюк?

Изменить: я проверил по G CC 10, и, похоже, он выполняет большую работу, как я ожидал, но все еще не объединяет наборы мемов.

...