對于這樣的功能,clang(有時gcc在某些情況下我無法最小限度地重現)似乎在-mavx2開關打開時會生成臃腫的代碼。
unsigned count(uint64_t *f) {
unsigned c = 0;
for (unsigned i = 0; i < 1024; i) {
if (sizeof(long) >= 8) {
c = __builtin_popcountl(f[i]);
} else {
c = __builtin_popcountll(f[i]);
}
}
return c;
}
這是來自gcc并且非常簡單。
count:
lea rcx, [rdi 8192]
xor eax, eax
.L2:
xor edx, edx
add rdi, 8
popcnt rdx, QWORD PTR [rdi-8]
add eax, edx
cmp rcx, rdi
jne .L2
ret
然而clang,決定在-mavx2開啟時產生這種巨大的膨脹。-mpopcnt也設定了。
.LCPI0_0:
.zero 32,15
.LCPI0_1:
.byte 0 # 0x0
.byte 1 # 0x1
.byte 1 # 0x1
.byte 2 # 0x2
.byte 1 # 0x1
.byte 2 # 0x2
.byte 2 # 0x2
.byte 3 # 0x3
.byte 1 # 0x1
.byte 2 # 0x2
.byte 2 # 0x2
.byte 3 # 0x3
.byte 2 # 0x2
.byte 3 # 0x3
.byte 3 # 0x3
.byte 4 # 0x4
.byte 0 # 0x0
.byte 1 # 0x1
.byte 1 # 0x1
.byte 2 # 0x2
.byte 1 # 0x1
.byte 2 # 0x2
.byte 2 # 0x2
.byte 3 # 0x3
.byte 1 # 0x1
.byte 2 # 0x2
.byte 2 # 0x2
.byte 3 # 0x3
.byte 2 # 0x2
.byte 3 # 0x3
.byte 3 # 0x3
.byte 4 # 0x4
count: # @count
vpxor xmm0, xmm0, xmm0
xor eax, eax
vmovdqa ymm1, ymmword ptr [rip .LCPI0_0] # ymm1 = [15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15]
vmovdqa ymm2, ymmword ptr [rip .LCPI0_1] # ymm2 = [0,1,1,2,1,2,2,3,1,2,2,3,2,3,3,4,0,1,1,2,1,2,2,3,1,2,2,3,2,3,3,4]
vpxor xmm12, xmm12, xmm12
vpxor xmm4, xmm4, xmm4
vpxor xmm5, xmm5, xmm5
vpxor xmm6, xmm6, xmm6
.LBB0_1: # =>This Inner Loop Header: Depth=1
vmovdqu ymm7, ymmword ptr [rdi 8*rax]
vmovdqu ymm8, ymmword ptr [rdi 8*rax 32]
vmovdqu ymm9, ymmword ptr [rdi 8*rax 64]
vmovdqu ymm10, ymmword ptr [rdi 8*rax 96]
vpand ymm11, ymm7, ymm1
vpshufb ymm11, ymm2, ymm11
vpsrlw ymm7, ymm7, 4
vpand ymm7, ymm7, ymm1
vpshufb ymm7, ymm2, ymm7
vpaddb ymm7, ymm11, ymm7
vpsadbw ymm7, ymm12, ymm7
vpand ymm11, ymm8, ymm1
vpshufb ymm11, ymm2, ymm11
vpsrlw ymm8, ymm8, 4
vpand ymm8, ymm8, ymm1
vpshufb ymm8, ymm2, ymm8
vpaddb ymm8, ymm8, ymm11
vpsadbw ymm8, ymm8, ymm12
vpand ymm11, ymm9, ymm1
vpshufb ymm11, ymm2, ymm11
vpsrlw ymm9, ymm9, 4
vpand ymm9, ymm9, ymm1
vpshufb ymm9, ymm2, ymm9
vpaddb ymm9, ymm9, ymm11
vpsadbw ymm9, ymm9, ymm12
vpand ymm11, ymm10, ymm1
vpshufb ymm11, ymm2, ymm11
vpsrlw ymm10, ymm10, 4
vpand ymm10, ymm10, ymm1
vpshufb ymm10, ymm2, ymm10
vpaddb ymm10, ymm10, ymm11
vpsadbw ymm10, ymm10, ymm12
vextracti128 xmm3, ymm7, 1
vpackusdw xmm3, xmm7, xmm3
vpaddd xmm0, xmm0, xmm3
vextracti128 xmm3, ymm8, 1
vpackusdw xmm3, xmm8, xmm3
vpaddd xmm4, xmm4, xmm3
vextracti128 xmm3, ymm9, 1
vpackusdw xmm3, xmm9, xmm3
vpaddd xmm5, xmm5, xmm3
vextracti128 xmm3, ymm10, 1
vpackusdw xmm3, xmm10, xmm3
vpaddd xmm6, xmm6, xmm3
add rax, 16
cmp rax, 1024
jne .LBB0_1
vpaddd xmm0, xmm4, xmm0
vpaddd xmm0, xmm5, xmm0
vpaddd xmm0, xmm6, xmm0
vpshufd xmm1, xmm0, 238 # xmm1 = xmm0[2,3,2,3]
vpaddd xmm0, xmm0, xmm1
vpshufd xmm1, xmm0, 85 # xmm1 = xmm0[1,1,1,1]
vpaddd xmm0, xmm0, xmm1
vmovd eax, xmm0
vzeroupper
ret
clang的代碼類似于gccwhen only-mpopcnt開啟,只是有點展開。
count: # @count
xor ecx, ecx
xor eax, eax
.LBB0_1: # =>This Inner Loop Header: Depth=1
popcnt rdx, qword ptr [rdi 8*rcx]
add edx, eax
popcnt rsi, qword ptr [rdi 8*rcx 8]
add esi, edx
popcnt rdx, qword ptr [rdi 8*rcx 16]
popcnt rax, qword ptr [rdi 8*rcx 24]
add edx, esi
add eax, edx
add rcx, 4
cmp rcx, 1024
jne .LBB0_1
ret
根據這份檔案(https://www.agner.org/optimize/instruction_tables.pdf),popcnt對于大多數架構來說,這是一個非常便宜的指令。那么,當我明確允許使用它時,為什么會clang產生這樣的膨脹來替換?優化級別全部設定為。popcnt-mpopcnt-O3
這是 godbolt 的鏈接(https://godbolt.org/z/4vWK33a7c)。
uj5u.com熱心網友回復:
它是自動矢量化和展開,這是大型陣列的性能優勢(或者如果 clang 的開銷較小的話),至少在英特爾 CPU 上popcnt是 1/時鐘,因此每個時鐘 64 位。(AMD Zen 有 3 或 4/clock popcnt,因此使用add等量的 4 個標量整數 ALU 埠的指令,它可以支持 2/clock uint64_t popcnt load 和 add。) https://uops.info/
但vpshufb在 Intel 上也是 1/clock(或在 Ice Lake 上為 2/clock),如果瓶頸是每個時鐘 128 位的 popcount 作業。(對 32 個位元組的低 4 位進行表查找。)但這肯定不會那么好,因為它在回圈內進行了所有額外的洗牌。:/
這種矢量化在 SIMD ALU 只有 256 位寬的 Zen1 上失敗了,但對英特爾來說應該是一個重大勝利,也許是 Zen2 及更高版本的勝利。
但是看起來clang在內部回圈內擴大到32位計數vpsadbw,所以它不如它可能的那么好。1024xuint64_t是 256__m256i個輸入資料向量,而 clang 是按 4 展開的,因此任何一個元素的最大計數只有 64,不會溢位。
考慮到它所做的作業量,Clang 正在展開一個驚人的數量。對我來說vextracti128并vpackusdw沒有多大意義,IDK 為什么它會在回圈中這樣做。沒有溢位風險的矢量化簡單方法就是vpsadbw-> vpaddqor vpaddd,它已經vpsadbw用于 8 位元組塊內的水平位元組總和。(更好的方法是將其推遲到位元組元素可能溢位之前,所以做一些vpaddb。就像如何使用 SIMD 計算字符出現次數一樣,盡管位元組計數器在那里只增加 0 或 1,而不是 0 .. 8)
請參閱Counting 1 bits (population count) on large data using AVX-512 or AVX-2,尤其是 Wojciech Mu?a 的 big-array popcnt 函式:https ://github.com/WojciechMula/sse-popcount/ - clang 使用相同的策略一樣,popcnt_AVX2_lookup但在迭代中累積結果的效率要低得多。
轉載請註明出處,本文鏈接:https://www.uj5u.com/net/411262.html
標籤:
上一篇:匯編程式編譯的作業原理
