[英]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.