2013-11-23 86 views
4

這裏是一個代碼段,以計算值的平方根當我掩飾此代碼從 http://felix.abecassis.me/2011/09/cpp-getting-started-with-sse/有沒有辦法利用所有的XMM寄存器?

void sse(float* a, int N) 
{ 
    // We assume N % 4 == 0. 
    int nb_iters = N/4; 
    __m128* ptr = (__m128*)a; 


    for (int i = 0; i < nb_iters; ++i, ++ptr, a += 4){ 
     _mm_store_ps(a, _mm_sqrt_ps(*ptr)); 
    } 

} 

截取的浮動陣列,我只看到一個XMM(XMM0)被使用。我假定展開循環會給編譯器一個提示,即可以使用更多的xmms。我修改代碼爲

void sse3(float* a, int N) 
{ 
__m128* ptr = (__m128*)a; 

for (int i = 0; i < N; i+=32){ 

    _mm_store_ps(a + i, _mm_sqrt_ps(*ptr)); 
    ptr++; 
    _mm_store_ps(a + i + 4, _mm_sqrt_ps(*ptr)); 
    ptr++; 
    _mm_store_ps(a + i + 8, _mm_sqrt_ps(*ptr)); 
    ptr++; 
    _mm_store_ps(a + i + 12, _mm_sqrt_ps(*ptr)); 
    ptr++; 
    _mm_store_ps(a + i + 16, _mm_sqrt_ps(*ptr)); 
    ptr++; 
    _mm_store_ps(a + i + 20, _mm_sqrt_ps(*ptr)); 
    ptr++; 
    _mm_store_ps(a + i + 24, _mm_sqrt_ps(*ptr)); 
    ptr++; 
    _mm_store_ps(a + i + 28, _mm_sqrt_ps(*ptr)); 
    ptr++; 
} 
} 

在這種情況下,N應該大於32。 但是我仍然看不到一個xmm。爲什麼編譯器不能分配多個xmm?

我的理解是對xmm0,xmm1,xmm2 ... xmm7的計算是獨立的,可以在現代超標量體系結構上並行運行。在一個4路超標量機器上,第二個代碼片段應該給我理論上的加速4(例如,如果分配4個不同的xmms)。

PS:第二個代碼片段看起來有點快(一致)。作爲建議

這裏

sse3: 18809 microseconds 
sse: 20543 microseconds 

修訂

使用-O3標誌是拆裝本福格特的答案 - 注意,我改變了函數的名稱SSE4。

147:ssetest.cpp **** void sse4(float* a, int N) 
148:ssetest.cpp **** { 
2076     .loc 8 148 0 
2077     .cfi_startproc 
2078    .LVL173: 
2079    .LBB5900: 
2080    .LBB5901: 
149:ssetest.cpp **** __m128 b, c, d, e; 
150:ssetest.cpp **** 
151:ssetest.cpp **** for (int i = 0; i < N; i += 16) { 
2081     .loc 8 151 0 
2082 0320 85F6   testl %esi, %esi # N 
2083 0322 7E4C   jle .L106 #, 
147:ssetest.cpp **** void sse4(float* a, int N) 
2084     .loc 8 147 0 
2085 0324 8D56FF  leal -1(%rsi), %edx #, tmp104 
2086    .LBE5901: 
2087    .LBE5900: 
2088 0327 31C0   xorl %eax, %eax # ivtmp.1046 
2089    .LBB5925: 
2090    .LBB5924: 
2091 0329 C1EA04  shrl $4, %edx #, 
2092 032c 4883C201  addq $1, %rdx #, D.189746 
2093 0330 48C1E206  salq $6, %rdx #, D.189746 
2094    .LVL174: 
2095     .p2align 4,,10 
2096 0334 0F1F4000  .p2align 3 
2097    .L108: 
2098    .LBB5902: 
2099    .LBB5903: 
899:/usr/lib/gcc/x86_64-linux-gnu/4.6/include/xmmintrin.h **** return (__m128) *(__v4sf *)__P; 
2100     .loc 9 899 0 discriminator 2 
2101 0338 0F285407  movaps 16(%rdi,%rax), %xmm2 # MEM[base: a_7(D), index: ivtmp.1046_85, offset: 16B], c 
2101  10 
2102    .LVL175: 
2103    .LBE5903: 
2104    .LBE5902: 
2105    .LBB5904: 
2106    .LBB5905: 
182:/usr/lib/gcc/x86_64-linux-gnu/4.6/include/xmmintrin.h **** return (__m128) __builtin_ia32_sqrtps ((__v4sf)__A); 
2107     .loc 9 182 0 discriminator 2 
2108 033d 0F511C07  sqrtps (%rdi,%rax), %xmm3 # MEM[base: a_7(D), index: ivtmp.1046_85, offset: 0B], tmp107 
2109    .LBE5905: 
2110    .LBE5904: 
2111    .LBB5906: 
2112    .LBB5907: 
899:/usr/lib/gcc/x86_64-linux-gnu/4.6/include/xmmintrin.h **** return (__m128) *(__v4sf *)__P; 
2113     .loc 9 899 0 discriminator 2 
2114 0341 0F284C07  movaps 32(%rdi,%rax), %xmm1 # MEM[base: a_7(D), index: ivtmp.1046_85, offset: 32B], d 
2114  20 
2115    .LVL176: 
2116    .LBE5907: 
2117    .LBE5906: 
2118    .LBB5908: 
2119    .LBB5909: 
182:/usr/lib/gcc/x86_64-linux-gnu/4.6/include/xmmintrin.h **** return (__m128) __builtin_ia32_sqrtps ((__v4sf)__A); 
2120     .loc 9 182 0 discriminator 2 
2121 0346 0F51D2  sqrtps %xmm2, %xmm2 # c, tmp109 
2122    .LBE5909: 
2123    .LBE5908: 
2124    .LBB5910: 
2125    .LBB5911: 
899:/usr/lib/gcc/x86_64-linux-gnu/4.6/include/xmmintrin.h **** return (__m128) *(__v4sf *)__P; 
2126     .loc 9 899 0 discriminator 2 
2127 0349 0F284407  movaps 48(%rdi,%rax), %xmm0 # MEM[base: a_7(D), index: ivtmp.1046_85, offset: 48B], e 
2127  30 
2128    .LVL177: 
2129    .LBE5911: 
2130    .LBE5910: 
2131    .LBB5912: 
2132    .LBB5913: 
182:/usr/lib/gcc/x86_64-linux-gnu/4.6/include/xmmintrin.h **** return (__m128) __builtin_ia32_sqrtps ((__v4sf)__A); 
2133     .loc 9 182 0 discriminator 2 
2134 034e 0F51C9  sqrtps %xmm1, %xmm1 # d, tmp111 
2135    .LBE5913: 
2136    .LBE5912: 
2137    .LBB5914: 
2138    .LBB5915: 
2139     .loc 9 948 0 discriminator 2 
2140 0351 0F291C07  movaps %xmm3, (%rdi,%rax) # tmp107, MEM[base: a_7(D), index: ivtmp.1046_85, offset: 0B] 
2141    .LVL178: 
2142    .LBE5915: 
2143    .LBE5914: 
2144    .LBB5916: 
2145    .LBB5917: 
182:/usr/lib/gcc/x86_64-linux-gnu/4.6/include/xmmintrin.h **** return (__m128) __builtin_ia32_sqrtps ((__v4sf)__A); 
2146     .loc 9 182 0 discriminator 2 
2147 0355 0F51C0  sqrtps %xmm0, %xmm0 # e, tmp113 
2148    .LBE5917: 
2149    .LBE5916: 
2150    .LBB5918: 
2151    .LBB5919: 
2152     .loc 9 948 0 discriminator 2 
2153 0358 0F295407  movaps %xmm2, 16(%rdi,%rax) # tmp109, MEM[base: a_7(D), index: ivtmp.1046_85, offset: 16B] 
2153  10 
2154    .LVL179: 
2155    .LBE5919: 
2156    .LBE5918: 
2157    .LBB5920: 
2158    .LBB5921: 
2159 035d 0F294C07  movaps %xmm1, 32(%rdi,%rax) # tmp111, MEM[base: a_7(D), index: ivtmp.1046_85, offset: 32B] 
2159  20 
2160    .LVL180: 
2161    .LBE5921: 
2162    .LBE5920: 
2163    .LBB5922: 
2164    .LBB5923: 
2165 0362 0F294407  movaps %xmm0, 48(%rdi,%rax) # tmp113, MEM[base: a_7(D), index: ivtmp.1046_85, offset: 48B] 
2165  30 
2166 0367 4883C040  addq $64, %rax #, ivtmp.1046 
2167    .LVL181: 
2168    .LBE5923: 
2169    .LBE5922: 
2170     .loc 8 151 0 discriminator 2 
2171 036b 4839D0  cmpq %rdx, %rax # D.189746, ivtmp.1046 
2172 036e 75C8   jne .L108 #, 
2173    .LVL182: 
2174    .L106: 
2175 0370 F3   rep 
2176 0371 C3   ret 
2177    .LBE5924: 
2178    .LBE5925: 
2179     .cfi_endproc 
2180    .LFE7998: 
2182 0372 66666666  .p2align 4,,15 
2182  662E0F1F 
2182  84000000 
2182  0000 
2183     .globl _Z6normalPfi 
2185    _Z6normalPfi: 
2186    .LFB7999: 
152:ssetest.cpp ****  b = _mm_load_ps(a + i); 
153:ssetest.cpp ****  c = _mm_load_ps(a + i + 4); 
154:ssetest.cpp ****  d = _mm_load_ps(a + i + 8); 
155:ssetest.cpp ****  e = _mm_load_ps(a + i + 12); 
156:ssetest.cpp ****  _mm_store_ps(a + i,  _mm_sqrt_ps(b)); 
157:ssetest.cpp ****  _mm_store_ps(a + i + 4, _mm_sqrt_ps(c)); 
158:ssetest.cpp ****  _mm_store_ps(a + i + 8, _mm_sqrt_ps(d)); 
159:ssetest.cpp ****  _mm_store_ps(a + i + 12, _mm_sqrt_ps(e)); 
160:ssetest.cpp **** } 
161:ssetest.cpp **** } 

奇怪的是,SSE和SSE4具有幾乎相同的性能和SSE3執行最差(雖然環路的一部分被展開)。

+1

看看[註冊重命名](http://en.wikipedia.org/wiki/Register_renaming)。因此,編譯器根本不需要使用多於1個寄存器... – Mysticial

+3

根據Anger Fog的着名手冊(http://www.agner.org/optimize/),對於Wolfdale CPU,指令SQRTPS具有6-13個週期的等待時間,5-12個週期的相互吞吐量,並且只能使用一個執行端口p0。爲您翻譯,只有一個SQRTPS指令可以在飛行中,而且每個指令只能每5-12個循環啓動一次。因此,併發執行SQRTPS指令的機會是零,並且與加法或乘法相比,它們需要花費很長時間才能完成。編譯器無法通過在此處打破更多寄存器來獲勝。 –

+0

@Mysticial在彙編代碼中有一個Read-After-Write依賴關係,xmm0在先前存儲到內存之後總是必須等待來自內存的加載。 – Mathai

回答

3

如何:

void sse3(float* a, int N) 
{ 
    __m128 b, c, d, e; 

    for (int i = 0; i < N; i += 16) { 
     b = _mm_load_ps(a + i); 
     c = _mm_load_ps(a + i + 4); 
     d = _mm_load_ps(a + i + 8); 
     e = _mm_load_ps(a + i + 12); 
     _mm_store_ps(a + i,  _mm_sqrt_ps(b)); 
     _mm_store_ps(a + i + 4, _mm_sqrt_ps(c)); 
     _mm_store_ps(a + i + 8, _mm_sqrt_ps(d)); 
     _mm_store_ps(a + i + 12, _mm_sqrt_ps(e)); 
    } 
} 

注意:使用多個計算相同的XMM寄存器是一兩件事,可以殺死流水線,但它不是唯一的東西。只有在有足夠數量的其他資源可用時,不同寄存器上的操作才能獨立進行。沒有整個SIMD計算單位專用於每個註冊,因爲您的評論表明您相信。

+0

感謝您的回覆。在反彙編中我仍然只能看到xmm0。速度沒有區別。 – Mathai

+0

@Mathai:你可以發佈拆卸嗎?我對速度沒有任何意外感到驚訝,但編譯器應該完全不同於對所有四個命名變量使用一個XMM寄存器,因爲它們的生命週期重疊。它做了很多指令重新排序嗎? –

+0

已過帳。我不確定重新排序的指令是什麼。 – Mathai

2

我想你在這裏看到的是防止鋸齒的編譯器。您正在通過指針進行讀寫操作,因此編譯器必須假定每次寫入任何float *都可能會改變下一次讀取float *。嘗試在你的指針上使用__restrict__來告訴編譯器你沒有這樣做。你也可以重寫你的循環來從一個數組計算(全局是理想的,因爲那時不會出現混淆)。

+0

他實際上並不是通過任何地方閱讀「float *」。但是,擺脫'a'和'ptr'之間的別名肯定會很好。 –

+0

@Ben Jackson,我不熟悉__restric__。我是否將其更改爲void sse3(float * __restrict__ a,int N)?我試過了,我在反彙編中只看到了xmm0。 – Mathai

+0

@BenVoigt指出,GCC似乎並不關心'float'和'__m128'之間的別名。我用'-funroll-all-loops'獲得了多個寄存器(它甚至處理了所有'nb_iters'不是展開倍數的情況。) –