簡體   English   中英

平鋪優化 gcc 與 clang

[英]Tiling optimization gcc vs clang

我一直在尋找使用平鋪方法的 L1 緩存優化。

這是我正在使用的優化技術:

// Original loop
void original_loop() {
  for (int i = 0; i < N; i++) {
    sum += array[i];
  }
}

// Loop tiling
void tiled_loop() {
  for (int i = 0; i < N; i += 16) {
    for (int j = 0; j < 16; j++) {
      sum += array[i + j];
    }
  }
}

然后有趣的是,我發現 clang 中的平鋪循環實際上比原始循環慢(或速度大致相同)。

這是我的基准測試代碼:

#include <chrono>
#include <iostream>

const int N = 10000;
const int blockSize = 16;
int array[N];
int sum;

// Original loop
void original_loop() {
  for (int i = 0; i < N; i++) {
    sum += array[i];
  }
}

// Loop tiling
void tiled_loop() {
  for (int i = 0; i < N; i += blockSize) {
    for (int j = 0; j < blockSize; j++) {
      sum += array[i + j];
    }
  }
}

int main() {
  // Initialize array
  for (int i = 0; i < N; i++) {
    array[i] = i;
  }

  // Benchmark original loop
  auto start = std::chrono::high_resolution_clock::now();
  for (int i = 0; i < 100000; i++) {
    sum = 0;
    original_loop();
  }
  auto end = std::chrono::high_resolution_clock::now();
  std::chrono::duration<double> elapsed = end - start;
  std::cout << "Original loop: " << elapsed.count() << " seconds" << std::endl;

  // Benchmark tiled loop
  start = std::chrono::high_resolution_clock::now();
  for (int i = 0; i < 100000; i++) {
    sum = 0;
    tiled_loop();
  }
  end = std::chrono::high_resolution_clock::now();
  elapsed = end - start;
  std::cout << "Tiled loop: " << elapsed.count() << " seconds" << std::endl;

  return 0;
}

使用gcc 12.2 and -O3以及clang 15.0.0 and -O3一些結果:

For N = 1000000 and blockSize = 16
**GCC**
Original loop: 11.1892 seconds
Tiled loop: 9.67448 seconds
**Clang**  
Original loop: 8.52184 seconds
Tiled loop: 8.67858 seconds

較小的數字:

For N = 10000 and blockSize = 16
**GCC**
Original loop: 0.094786 seconds
Tiled loop: 0.0436597 seconds
**Clang**
Original loop: 0.0416874 seconds
Tiled loop: 0.0610718 seconds

我已經試過很多次了。 Clang 與原始循環相同或更差。 有時它會比原來的更好,但即便如此,差異也沒有 gcc 顯着。有什么想法嗎?

這是程序集(我找不到要在此處發布的程序集代碼的鏈接。也許有人可以用 godbolt 鏈接替換此代碼):

GCC:

original_loop():
        mov     ecx, DWORD PTR sum[rip]
        mov     eax, OFFSET FLAT:array
        mov     edx, OFFSET FLAT:array+40000
        pxor    xmm0, xmm0
.L2:
        paddd   xmm0, XMMWORD PTR [rax]
        add     rax, 16
        cmp     rdx, rax
        jne     .L2
        movdqa  xmm1, xmm0
        psrldq  xmm1, 8
        paddd   xmm0, xmm1
        movdqa  xmm1, xmm0
        psrldq  xmm1, 4
        paddd   xmm0, xmm1
        movd    eax, xmm0
        add     eax, ecx
        mov     DWORD PTR sum[rip], eax
        ret
tiled_loop():
        pxor    xmm1, xmm1
        mov     eax, OFFSET FLAT:array
        mov     edx, OFFSET FLAT:array+40000
        movd    xmm3, DWORD PTR sum[rip]
        movdqa  xmm2, xmm1
        movdqa  xmm0, xmm1
.L6:
        paddd   xmm3, XMMWORD PTR [rax]
        paddd   xmm0, XMMWORD PTR [rax+16]
        add     rax, 64
        paddd   xmm2, XMMWORD PTR [rax-32]
        paddd   xmm1, XMMWORD PTR [rax-16]
        cmp     rdx, rax
        jne     .L6
        paddd   xmm0, xmm3
        paddd   xmm0, xmm2
        paddd   xmm0, xmm1
        movdqa  xmm1, xmm0
        psrldq  xmm1, 8
        paddd   xmm0, xmm1
        movdqa  xmm1, xmm0
        psrldq  xmm1, 4
        paddd   xmm0, xmm1
        movd    DWORD PTR sum[rip], xmm0
        ret
sum:
        .zero   4
array:
        .zero   40000

Clang:

original_loop():                     # @original_loop()
        pxor    xmm0, xmm0
        mov     eax, 12
        movd    xmm1, dword ptr [rip + sum]     # xmm1 = mem[0],zero,zero,zero
        lea     rcx, [rip + array]
.LBB0_1:                                # =>This Inner Loop Header: Depth=1
        paddd   xmm1, xmmword ptr [rcx + 4*rax - 48]
        paddd   xmm0, xmmword ptr [rcx + 4*rax - 32]
        paddd   xmm1, xmmword ptr [rcx + 4*rax - 16]
        paddd   xmm0, xmmword ptr [rcx + 4*rax]
        add     rax, 16
        cmp     rax, 10012
        jne     .LBB0_1
        paddd   xmm0, xmm1
        pshufd  xmm1, xmm0, 238                 # xmm1 = xmm0[2,3,2,3]
        paddd   xmm1, xmm0
        pshufd  xmm0, xmm1, 85                  # xmm0 = xmm1[1,1,1,1]
        paddd   xmm0, xmm1
        movd    dword ptr [rip + sum], xmm0
        ret
tiled_loop():                        # @tiled_loop()
        mov     edx, dword ptr [rip + sum]
        xor     eax, eax
        lea     rcx, [rip + array]
.LBB1_1:                                # =>This Inner Loop Header: Depth=1
        movdqa  xmm0, xmmword ptr [rcx + 4*rax]
        movdqa  xmm1, xmmword ptr [rcx + 4*rax + 16]
        paddd   xmm1, xmmword ptr [rcx + 4*rax + 48]
        paddd   xmm0, xmmword ptr [rcx + 4*rax + 32]
        paddd   xmm0, xmm1
        pshufd  xmm1, xmm0, 238                 # xmm1 = xmm0[2,3,2,3]
        paddd   xmm1, xmm0
        pshufd  xmm0, xmm1, 85                  # xmm0 = xmm1[1,1,1,1]
        paddd   xmm0, xmm1
        movd    esi, xmm0
        add     esi, edx
        cmp     rax, 9983
        ja      .LBB1_3
        movdqa  xmm0, xmmword ptr [rcx + 4*rax + 64]
        movdqa  xmm1, xmmword ptr [rcx + 4*rax + 80]
        paddd   xmm1, xmmword ptr [rcx + 4*rax + 112]
        paddd   xmm0, xmmword ptr [rcx + 4*rax + 96]
        paddd   xmm0, xmm1
        pshufd  xmm1, xmm0, 238                 # xmm1 = xmm0[2,3,2,3]
        paddd   xmm1, xmm0
        pshufd  xmm0, xmm1, 85                  # xmm0 = xmm1[1,1,1,1]
        paddd   xmm0, xmm1
        movd    edx, xmm0
        add     edx, esi
        add     rax, 32
        jmp     .LBB1_1
.LBB1_3:
        mov     dword ptr [rip + sum], esi
        ret
array:
        .zero   40000

sum:
        .long   0 

編輯

帶有-march=native標志

Original loop: 0.0292406 seconds
Tiled loop: 0.173324 seconds

clang 性能差 10 倍

GCC 和 Clang 通常以不同的方式優化事物。 您沒有使用-march如果您想要最佳結果,您可能需要它。 使用-O3 -march=skylake , Clang 會自動展開循環,如果您在original_loop()中添加#pragma GCC unroll 16 , GCC 將生成類似的代碼: https://godbolt.org/z/WWr1nToaG

通過平鋪,GCC 的代碼還可以,但 Clang 的代碼非常臃腫: https://godbolt.org/z/GYqYTc7cc - 我對 Clang 代碼運行速度較慢並不感到驚訝。

如果你打算使用這樣的技巧來嘗試讓編譯器生成更好的代碼,你必須接受它是特定於編譯器的。 對一種設置有幫助的調整可能會損害另一種設置,而且預測起來並不容易。 通常,如果您編寫像original_loop()這樣的簡單代碼,至少給定的編譯器會在新版本發布時趨於改進(或保持不變)。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM