我一直在試圖找出應用程序中的性能問題,並最終將其縮小爲一個非常奇怪的問題。如果VZEROUPPER
指令被註釋掉,以下代碼片段在Skylake CPU(i5-6500)上運行速度會減慢6倍。我測試過Sandy Bridge和Ivy Bridge CPU,兩個版本都以相同的速度運行,不管有沒有VZEROUPPER
。爲什麼這個SSE代碼在Skylake上沒有VZEROUPPER慢6倍?
現在我對VZEROUPPER
做的事情有了一個相當好的想法,我認爲在沒有VEX編碼指令並且沒有調用任何可能包含它們的函數的情況下,對代碼應該沒有任何影響。它不支持其他支持AVX的CPU似乎支持這一點。那麼表11-2中的Intel® 64 and IA-32 Architectures Optimization Reference Manual
那麼是怎麼回事?
我唯一留下的理論是,CPU中存在一個錯誤,它不正確地觸發了「不保存AVX寄存器的上半部分」過程。或別的東西一樣奇怪。
這是main.cpp中:
#include <immintrin.h>
int slow_function(double i_a, double i_b, double i_c);
int main()
{
/* DAZ and FTZ, does not change anything here. */
_mm_setcsr(_mm_getcsr() | 0x8040);
/* This instruction fixes performance. */
__asm__ __volatile__ ("vzeroupper" : : :);
int r = 0;
for(unsigned j = 0; j < 100000000; ++j)
{
r |= slow_function(
0.84445079384884236262,
-6.1000481519580951328,
5.0302160279288017364);
}
return r;
}
,這是slow_function.cpp:
#include <immintrin.h>
int slow_function(double i_a, double i_b, double i_c)
{
__m128d sign_bit = _mm_set_sd(-0.0);
__m128d q_a = _mm_set_sd(i_a);
__m128d q_b = _mm_set_sd(i_b);
__m128d q_c = _mm_set_sd(i_c);
int vmask;
const __m128d zero = _mm_setzero_pd();
__m128d q_abc = _mm_add_sd(_mm_add_sd(q_a, q_b), q_c);
if(_mm_comigt_sd(q_c, zero) && _mm_comigt_sd(q_abc, zero) )
{
return 7;
}
__m128d discr = _mm_sub_sd(
_mm_mul_sd(q_b, q_b),
_mm_mul_sd(_mm_mul_sd(q_a, q_c), _mm_set_sd(4.0)));
__m128d sqrt_discr = _mm_sqrt_sd(discr, discr);
__m128d q = sqrt_discr;
__m128d v = _mm_div_pd(
_mm_shuffle_pd(q, q_c, _MM_SHUFFLE2(0, 0)),
_mm_shuffle_pd(q_a, q, _MM_SHUFFLE2(0, 0)));
vmask = _mm_movemask_pd(
_mm_and_pd(
_mm_cmplt_pd(zero, v),
_mm_cmple_pd(v, _mm_set1_pd(1.0))));
return vmask + 1;
}
功能編譯成這個與鐺:
0: f3 0f 7e e2 movq %xmm2,%xmm4
4: 66 0f 57 db xorpd %xmm3,%xmm3
8: 66 0f 2f e3 comisd %xmm3,%xmm4
c: 76 17 jbe 25 <_Z13slow_functionddd+0x25>
e: 66 0f 28 e9 movapd %xmm1,%xmm5
12: f2 0f 58 e8 addsd %xmm0,%xmm5
16: f2 0f 58 ea addsd %xmm2,%xmm5
1a: 66 0f 2f eb comisd %xmm3,%xmm5
1e: b8 07 00 00 00 mov $0x7,%eax
23: 77 48 ja 6d <_Z13slow_functionddd+0x6d>
25: f2 0f 59 c9 mulsd %xmm1,%xmm1
29: 66 0f 28 e8 movapd %xmm0,%xmm5
2d: f2 0f 59 2d 00 00 00 mulsd 0x0(%rip),%xmm5 # 35 <_Z13slow_functionddd+0x35>
34: 00
35: f2 0f 59 ea mulsd %xmm2,%xmm5
39: f2 0f 58 e9 addsd %xmm1,%xmm5
3d: f3 0f 7e cd movq %xmm5,%xmm1
41: f2 0f 51 c9 sqrtsd %xmm1,%xmm1
45: f3 0f 7e c9 movq %xmm1,%xmm1
49: 66 0f 14 c1 unpcklpd %xmm1,%xmm0
4d: 66 0f 14 cc unpcklpd %xmm4,%xmm1
51: 66 0f 5e c8 divpd %xmm0,%xmm1
55: 66 0f c2 d9 01 cmpltpd %xmm1,%xmm3
5a: 66 0f c2 0d 00 00 00 cmplepd 0x0(%rip),%xmm1 # 63 <_Z13slow_functionddd+0x63>
61: 00 02
63: 66 0f 54 cb andpd %xmm3,%xmm1
67: 66 0f 50 c1 movmskpd %xmm1,%eax
6b: ff c0 inc %eax
6d: c3 retq
所生成的代碼與gcc不同,但它顯示了同樣的問題。舊版本的intel編譯器會產生功能的另一個變體,它也會顯示問題,但前提是main.cpp
不是由intel編譯器構建的,因爲它會插入調用來初始化某些自己的庫,這些庫可能最終會在某處執行VZEROUPPER
。
當然,如果整個東西都是用AVX支持構建的,所以內在函數變成了VEX編碼指令,也沒有問題。
我已經試過用linux在perf
上分析代碼,大部分運行時通常在1-2條指令上,但並不總是相同的,這取決於我的配置文件(gcc,clang,intel)的哪個版本。縮短功能看起來會使性能差異逐漸消失,因此看起來好像有幾條指令導致了這個問題。
編輯:這是一個純的程序集版本,爲Linux。下面的評論。
.text
.p2align 4, 0x90
.globl _start
_start:
#vmovaps %ymm0, %ymm1 # This makes SSE code crawl.
#vzeroupper # This makes it fast again.
movl $100000000, %ebp
.p2align 4, 0x90
.LBB0_1:
xorpd %xmm0, %xmm0
xorpd %xmm1, %xmm1
xorpd %xmm2, %xmm2
movq %xmm2, %xmm4
xorpd %xmm3, %xmm3
movapd %xmm1, %xmm5
addsd %xmm0, %xmm5
addsd %xmm2, %xmm5
mulsd %xmm1, %xmm1
movapd %xmm0, %xmm5
mulsd %xmm2, %xmm5
addsd %xmm1, %xmm5
movq %xmm5, %xmm1
sqrtsd %xmm1, %xmm1
movq %xmm1, %xmm1
unpcklpd %xmm1, %xmm0
unpcklpd %xmm4, %xmm1
decl %ebp
jne .LBB0_1
mov $0x1, %eax
int $0x80
好的,正如在評論中懷疑的那樣,使用VEX編碼指令會導致放緩。使用VZEROUPPER
清除它。但這仍然無法解釋原因。
據我所知,不使用VZEROUPPER
應該涉及過渡到舊的SSE指令的成本,但不是永久減速。尤其不是那麼大。考慮到循環開銷,這個比率至少是10倍,或許更多。
我已經嘗試搞亂組裝一點點和浮動指令一樣糟糕的雙重。我無法將問題指向單個指令。
你使用了哪些編譯器標誌?也許(隱藏)進程初始化使用了一些VEX指令,它將您置於混合狀態,從此您永遠不會退出。您可以嘗試複製/粘貼程序集並將其構建爲帶'_start'的純裝配程序,這樣可以避免任何編譯器插入的init代碼,並查看它是否表現出相同的問題。 – BeeOnRope
@BeeOnRope我使用'-O3 -ffast-math',但即使使用'-O0',效果也是存在的。我會嘗試純粹的組裝。你可能會在[Agner的博客](http://agner.org/optimize/blog/read.php?i=415)上發現的內容,發現VEX轉換的方式有很大的內部變化處理...將需要看看。 – Olivier
是的 - 但奇怪的是,在Skylake上,對於運行在「壞」混合模式下的處罰應該大大減少 - 但我沒有重新閱讀它,以便更新我對細節的記憶。 – BeeOnRope