[英]gcc -O0 outperforming -O3 on matrix sizes that are powers of 2 (matrix transpositions)
(出於測試目的)我編寫了一個簡單的方法來計算nxn矩陣的轉置
void transpose(const size_t _n, double* _A) {
for(uint i=0; i < _n; ++i) {
for(uint j=i+1; j < _n; ++j) {
double tmp = _A[i*_n+j];
_A[i*_n+j] = _A[j*_n+i];
_A[j*_n+i] = tmp;
}
}
}
當使用優化級別O3或Ofast時,我期望編譯器展開一些循環,這將導致更高的性能,尤其是當矩陣大小是2的倍數(即,每次迭代可以執行雙循環體)或類似時。 相反,我測量的恰恰相反。 2的權力實際上表明執行時間顯着增加。
這些尖峰也是64的固定間隔,間隔128更明顯,依此類推。 每個尖峰延伸到相鄰的矩陣大小,如下表所示
size n time(us)
1020 2649
1021 2815
1022 3100
1023 5428
1024 15791
1025 6778
1026 3106
1027 2847
1028 2660
1029 3038
1030 2613
我使用gcc版本4.8.2編譯但是同樣的事情發生在clang 3.5上,所以這可能是一些通用的東西?
所以我的問題基本上是:為什么執行時間周期性增加? 是否有一些通用的東西與任何優化選項一起出現(就像clang和gcc一樣)? 如果是這樣的優化選項導致了這個?
這怎么可能如此重要,即使O0版本的程序在512的倍數時優於03版本?
編輯:注意此(對數)圖中峰值的大小。 轉換具有優化的1024x1024矩陣實際上花費的時間與在沒有優化的情況下轉置1300x1300矩陣一樣多。 如果這是一個緩存故障/頁面錯誤問題,那么有人需要向我解釋為什么內存布局對於程序的優化版本來說是如此顯着不同,它失敗了2的權限,只是為了恢復高性能稍大的矩陣。 緩存故障是否應該創建更多類似步驟的模式? 為什么執行時間會再次下降? (為什么優化會創建以前不存在的緩存錯誤?)
編輯:以下應該是gcc生成的匯編代碼
沒有優化(O0):
_Z9transposemRPd:
.LFB0:
.cfi_startproc
push rbp
.cfi_def_cfa_offset 16
.cfi_offset 6, -16
mov rbp, rsp
.cfi_def_cfa_register 6
mov QWORD PTR [rbp-24], rdi
mov QWORD PTR [rbp-32], rsi
mov DWORD PTR [rbp-4], 0
jmp .L2
.L5:
mov eax, DWORD PTR [rbp-4]
add eax, 1
mov DWORD PTR [rbp-8], eax
jmp .L3
.L4:
mov rax, QWORD PTR [rbp-32]
mov rdx, QWORD PTR [rax]
mov eax, DWORD PTR [rbp-4]
imul rax, QWORD PTR [rbp-24]
mov rcx, rax
mov eax, DWORD PTR [rbp-8]
add rax, rcx
sal rax, 3
add rax, rdx
mov rax, QWORD PTR [rax]
mov QWORD PTR [rbp-16], rax
mov rax, QWORD PTR [rbp-32]
mov rdx, QWORD PTR [rax]
mov eax, DWORD PTR [rbp-4]
imul rax, QWORD PTR [rbp-24]
mov rcx, rax
mov eax, DWORD PTR [rbp-8]
add rax, rcx
sal rax, 3
add rdx, rax
mov rax, QWORD PTR [rbp-32]
mov rcx, QWORD PTR [rax]
mov eax, DWORD PTR [rbp-8]
imul rax, QWORD PTR [rbp-24]
mov rsi, rax
mov eax, DWORD PTR [rbp-4]
add rax, rsi
sal rax, 3
add rax, rcx
mov rax, QWORD PTR [rax]
mov QWORD PTR [rdx], rax
mov rax, QWORD PTR [rbp-32]
mov rdx, QWORD PTR [rax]
mov eax, DWORD PTR [rbp-8]
imul rax, QWORD PTR [rbp-24]
mov rcx, rax
mov eax, DWORD PTR [rbp-4]
add rax, rcx
sal rax, 3
add rdx, rax
mov rax, QWORD PTR [rbp-16]
mov QWORD PTR [rdx], rax
add DWORD PTR [rbp-8], 1
.L3:
mov eax, DWORD PTR [rbp-8]
cmp rax, QWORD PTR [rbp-24]
jb .L4
add DWORD PTR [rbp-4], 1
.L2:
mov eax, DWORD PTR [rbp-4]
cmp rax, QWORD PTR [rbp-24]
jb .L5
pop rbp
.cfi_def_cfa 7, 8
ret
.cfi_endproc
.LFE0:
.size _Z9transposemRPd, .-_Z9transposemRPd
.ident "GCC: (Debian 4.8.2-15) 4.8.2"
.section .note.GNU-stack,"",@progbits
優化(O3)
_Z9transposemRPd:
.LFB0:
.cfi_startproc
push rbx
.cfi_def_cfa_offset 16
.cfi_offset 3, -16
xor r11d, r11d
xor ebx, ebx
.L2:
cmp r11, rdi
mov r9, r11
jae .L10
.p2align 4,,10
.p2align 3
.L7:
add ebx, 1
mov r11d, ebx
cmp rdi, r11
mov rax, r11
jbe .L2
mov r10, r9
mov r8, QWORD PTR [rsi]
mov edx, ebx
imul r10, rdi
.p2align 4,,10
.p2align 3
.L6:
lea rcx, [rax+r10]
add edx, 1
imul rax, rdi
lea rcx, [r8+rcx*8]
movsd xmm0, QWORD PTR [rcx]
add rax, r9
lea rax, [r8+rax*8]
movsd xmm1, QWORD PTR [rax]
movsd QWORD PTR [rcx], xmm1
movsd QWORD PTR [rax], xmm0
mov eax, edx
cmp rdi, rax
ja .L6
cmp r11, rdi
mov r9, r11
jb .L7
.L10:
pop rbx
.cfi_def_cfa_offset 8
ret
.cfi_endproc
.LFE0:
.size _Z9transposemRPd, .-_Z9transposemRPd
.ident "GCC: (Debian 4.8.2-15) 4.8.2"
.section .note.GNU-stack,"",@progbits
執行時間的周期性增加必須歸因於緩存僅是N路關聯而不是完全關聯。 您正在目睹與緩存行選擇算法相關的哈希沖突。
最快的L1緩存具有比下一級L2更少的緩存行數。 在每個級別中,每個高速緩存行只能從有限的一組源中填充。
高速緩存行選擇算法的典型硬件實現將僅使用來自存儲器地址的少量位來確定應在哪個高速緩存槽中寫入數據 - 在HW位移位中是空閑的。
這導致存儲器范圍之間的競爭,例如在地址0x300010和0x341010之間。 在完全順序算法中,這無關緊要 - N對於幾乎所有形式的算法都足夠大:
for (i=0;i<1000;i++) a[i] += b[i] * c[i] + d[i];
但是當輸入(或輸出)的數量變大時,這在算法被優化時在內部發生,在高速緩存中具有一個輸入迫使另一輸入離開高速緩存。
// one possible method of optimization with 2 outputs and 6 inputs
// with two unrelated execution paths -- should be faster, but maybe it isn't
for (i=0;i<500;i++) {
a[i] += b[i] * c[i] + d[i];
a[i+500] += b[i+500] * c[i+500] + d[i+500];
}
示例5中的圖表:高速緩存關聯性說明矩陣行之間的512字節偏移是特定系統的全局最壞情況維度。 當這已知時,工作緩解是將矩陣水平過度分配到某個其他維度char matrix[512][512 + 64]
。
性能的提高可能與CPU / RAM緩存有關。
當數據不是2的冪時,高速緩存行負載(如16,32或64個字)的轉移比捆綁總線所需的數據傳輸更多 - 結果是無用的。 對於功率為2的數據集,使用所有預取數據。
我打賭如果你要禁用L1和L2緩存,性能將是完全平滑和可預測的。 但它會慢得多。 緩存真的有助於提高性能!
用代碼注釋:在-O3情況下,用
#include <cstdlib>
extern void transpose(const size_t n, double* a)
{
for (size_t i = 0; i < n; ++i) {
for (size_t j = i + 1; j < n; ++j) {
std::swap(a[i * n + j], a[j * n + i]); // or your expanded version.
}
}
}
用...編譯
$ g++ --version
g++ (Ubuntu/Linaro 4.8.1-10ubuntu9) 4.8.1
...
$ g++ -g1 -std=c++11 -Wall -o test.S -S test.cpp -O3
我明白了
_Z9transposemPd:
.LFB68:
.cfi_startproc
.LBB2:
testq %rdi, %rdi
je .L1
leaq 8(,%rdi,8), %r10
xorl %r8d, %r8d
.LBB3:
addq $1, %r8
leaq -8(%r10), %rcx
cmpq %rdi, %r8
leaq (%rsi,%rcx), %r9
je .L1
.p2align 4,,10
.p2align 3
.L10:
movq %r9, %rdx
movq %r8, %rax
.p2align 4,,10
.p2align 3
.L5:
.LBB4:
movsd (%rdx), %xmm1
movsd (%rsi,%rax,8), %xmm0
movsd %xmm1, (%rsi,%rax,8)
.LBE4:
addq $1, %rax
.LBB5:
movsd %xmm0, (%rdx)
addq %rcx, %rdx
.LBE5:
cmpq %rdi, %rax
jne .L5
addq $1, %r8
addq %r10, %r9
addq %rcx, %rsi
cmpq %rdi, %r8
jne .L10
.L1:
rep ret
.LBE3:
.LBE2:
.cfi_endproc
如果我添加-m32,那就完全不同了。
(注意:無論我使用std :: swap還是你的變體,它對程序集沒有任何影響)
但是,為了理解導致峰值的原因,您可能希望可視化正在進行的內存操作。
要添加到其他人: g++ -std=c++11 -march=core2 -O3 -c -S
-gcc version 4.8.2(MacPorts gcc48 4.8.2_0) - x86_64-apple-darwin13.0.0:
__Z9transposemPd:
LFB0:
testq %rdi, %rdi
je L1
leaq 8(,%rdi,8), %r10
xorl %r8d, %r8d
leaq -8(%r10), %rcx
addq $1, %r8
leaq (%rsi,%rcx), %r9
cmpq %rdi, %r8
je L1
.align 4,0x90
L10:
movq %r9, %rdx
movq %r8, %rax
.align 4,0x90
L5:
movsd (%rdx), %xmm0
movsd (%rsi,%rax,8), %xmm1
movsd %xmm0, (%rsi,%rax,8)
addq $1, %rax
movsd %xmm1, (%rdx)
addq %rcx, %rdx
cmpq %rdi, %rax
jne L5
addq $1, %r8
addq %r10, %r9
addq %rcx, %rsi
cmpq %rdi, %r8
jne L10
L1:
rep; ret
與@ ksfone的代碼基本相同,用於:
#include <cstddef>
void transpose(const size_t _n, double* _A) {
for(size_t i=0; i < _n; ++i) {
for(size_t j=i+1; j < _n; ++j) {
double tmp = _A[i*_n+j];
_A[i*_n+j] = _A[j*_n+i];
_A[j*_n+i] = tmp;
}
}
}
除了Mach-O'作為'差異(額外的下划線,對齊和DWARF位置),它也是一樣的。 但與OP的裝配輸出有很大不同。 一個更“緊密”的內循環。
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.