前の記事で ULxx の median decode が遅くなると書きました。実際どうなってるのかという話です。
gradient/left の decode は遅くなっていないので、怪しいのは tuned_RestoreCylindricalWrongMedian8 ということになります。最後の for ループの部分は以下のようになっています。
for (; p < pSrcEnd; p++, q++) { __m128i top = _mm_cvtsi32_si128(*(const uint32_t *)(q - cbStride)); __m128i grad = _mm_add_epi8(left, _mm_sub_epi8(top, topleft)); __m128i pred = _mm_max_epu8(_mm_min_epu8(_mm_max_epu8(left, top), grad), _mm_min_epu8(left, top)); __m128i value = _mm_add_epi8(pred, _mm_cvtsi32_si128(*(const uint32_t *)p)); *q = _mm_cvtsi128_si32(value); left = value; topleft = top; }
ただし、pSrcEnd と cbStride は関数の引数です。
これが VS2015 だと以下のようになります。
000007FEB485D680 4D 85 C0 test r8,r8 000007FEB485D683 74 48 je tuned_RestoreCylindricalWrongMedian8<6>+17Dh (07FEB485D6CDh) 000007FEB485D685 49 F7 DB neg r11 000007FEB485D688 48 2B D1 sub rdx,rcx 000007FEB485D68B 0F 1F 44 00 00 nop dword ptr [rax+rax] 000007FEB485D690 C4 C1 79 6E 2C 0B vmovd xmm5,dword ptr [r11+rcx] 000007FEB485D696 C5 D9 DE D5 vpmaxub xmm2,xmm4,xmm5 000007FEB485D69A C5 D1 F8 C0 vpsubb xmm0,xmm5,xmm0 000007FEB485D69E C5 F9 FC CC vpaddb xmm1,xmm0,xmm4 000007FEB485D6A2 C5 F9 6E 04 0A vmovd xmm0,dword ptr [rdx+rcx] 000007FEB485D6A7 C5 E9 DA D9 vpminub xmm3,xmm2,xmm1 000007FEB485D6AB C5 D9 DA D5 vpminub xmm2,xmm4,xmm5 000007FEB485D6AF C5 E1 DE E2 vpmaxub xmm4,xmm3,xmm2 000007FEB485D6B3 C5 D9 FC E0 vpaddb xmm4,xmm4,xmm0 000007FEB485D6B7 C5 F9 7E E0 vmovd eax,xmm4 000007FEB485D6BB 49 FF C1 inc r9 000007FEB485D6BE 88 01 mov byte ptr [rcx],al 000007FEB485D6C0 48 8D 49 01 lea rcx,[rcx+1] 000007FEB485D6C4 C5 FA 6F C5 vmovdqu xmm0,xmm5 000007FEB485D6C8 4D 3B C8 cmp r9,r8 000007FEB485D6CB 72 C3 jb tuned_RestoreCylindricalWrongMedian8<6>+140h (07FEB485D690h)
x86 ではメモリアクセスのアドレスの指定でレジスタを2つ使う場合に「レジスタ+レジスタ」しか使えない(「レジスタ-レジスタ」は使えない)ので、先に -cbStride を計算しておいてから、 q + (-cbStride) の形に置き換えているわけです。
で、VS2017 ではどうなるかというと、
000007FEB4795E70 4D 85 C0 test r8,r8 000007FEB4795E73 74 4B je tuned_RestoreCylindricalWrongMedian8<6>+180h (07FEB4795EC0h) 000007FEB4795E75 48 2B D1 sub rdx,rcx 000007FEB4795E78 0F 1F 84 00 00 00 00 00 nop dword ptr [rax+rax] 000007FEB4795E80 48 8B C1 mov rax,rcx 000007FEB4795E83 49 FF C1 inc r9 000007FEB4795E86 49 2B C3 sub rax,r11 000007FEB4795E89 C5 F9 6E 28 vmovd xmm5,dword ptr [rax] 000007FEB4795E8D C5 D9 DE D5 vpmaxub xmm2,xmm4,xmm5 000007FEB4795E91 C5 D1 F8 C0 vpsubb xmm0,xmm5,xmm0 000007FEB4795E95 C5 F9 FC CC vpaddb xmm1,xmm0,xmm4 000007FEB4795E99 C5 F9 6E 04 0A vmovd xmm0,dword ptr [rdx+rcx] 000007FEB4795E9E C5 E9 DA D9 vpminub xmm3,xmm2,xmm1 000007FEB4795EA2 C5 D9 DA D5 vpminub xmm2,xmm4,xmm5 000007FEB4795EA6 C5 E1 DE E2 vpmaxub xmm4,xmm3,xmm2 000007FEB4795EAA C5 D9 FC E0 vpaddb xmm4,xmm4,xmm0 000007FEB4795EAE C5 F9 7E E0 vmovd eax,xmm4 000007FEB4795EB2 88 01 mov byte ptr [rcx],al 000007FEB4795EB4 48 FF C1 inc rcx 000007FEB4795EB7 C5 FA 6F C5 vmovdqu xmm0,xmm5 000007FEB4795EBB 4D 3B C8 cmp r9,r8 000007FEB4795EBE 72 C0 jb tuned_RestoreCylindricalWrongMedian8<6>+140h (07FEB4795E80h)
なんでやねん。ループ外に出してよ。
じゃあソースコードのレベルで明示的に -cbStride を計算したらいいかというとそうはいかず、「最適化」をして同じバイナリを吐いてきます。cbStride を関数に渡すのではなく最初から -cbStride を渡すことにしてアドレスの計算が加算になるようにすると、当然ながら以下のようなバイナリになります。
000007FEB6ED5E70 4D 85 C0 test r8,r8 000007FEB6ED5E73 74 47 je tuned_RestoreCylindricalWrongMedian8<6>+16Ch (07FEB6ED5EBCh) 000007FEB6ED5E75 48 2B D1 sub rdx,rcx 000007FEB6ED5E78 0F 1F 84 00 00 00 00 00 nop dword ptr [rax+rax] 000007FEB6ED5E80 C5 F9 6E 2C 19 vmovd xmm5,dword ptr [rcx+rbx] 000007FEB6ED5E85 C5 D9 DE D5 vpmaxub xmm2,xmm4,xmm5 000007FEB6ED5E89 C5 D1 F8 C0 vpsubb xmm0,xmm5,xmm0 000007FEB6ED5E8D C5 F9 FC CC vpaddb xmm1,xmm0,xmm4 000007FEB6ED5E91 C5 F9 6E 04 0A vmovd xmm0,dword ptr [rdx+rcx] 000007FEB6ED5E96 C5 E9 DA D9 vpminub xmm3,xmm2,xmm1 000007FEB6ED5E9A C5 D9 DA D5 vpminub xmm2,xmm4,xmm5 000007FEB6ED5E9E C5 E1 DE E2 vpmaxub xmm4,xmm3,xmm2 000007FEB6ED5EA2 C5 D9 FC E0 vpaddb xmm4,xmm4,xmm0 000007FEB6ED5EA6 C5 F9 7E E0 vmovd eax,xmm4 000007FEB6ED5EAA 49 FF C1 inc r9 000007FEB6ED5EAD 88 01 mov byte ptr [rcx],al 000007FEB6ED5EAF 48 8D 49 01 lea rcx,[rcx+1] 000007FEB6ED5EB3 C5 FA 6F C5 vmovdqu xmm0,xmm5 000007FEB6ED5EB7 4D 3B C8 cmp r9,r8 000007FEB6ED5EBA 72 C4 jb tuned_RestoreCylindricalWrongMedian8<6>+130h (07FEB6ED5E80h)
なお、処理対象のラインの前のラインにもアクセスするというのは median decode に限らないのですが、median predict や gradient/left の場合とは違ってこの部分はベクトル化できず、16倍の影響が出て計測誤差に埋もれず観測されたということだと思われます。
しかしこれ、よく見ると VS2015 の場合でもまだ最適解じゃないですね。(r9 使わないで rcx で終了判定すべき)
no comment untill now