2013-02-14 52 views
2

閱讀this interesting article on the results of intrinsic-guided optimization of SSE code in different C++ compilers我決定自己做一個測試,特別是因爲這個帖子已經過了幾年了。我使用了MSVC,它在帖子作者的測試中做得很差(雖然在VS 2010版本中),並決定堅持一個非常基本的場景:將一些值打包到XMM寄存器中,並進行簡單操作,如添加。在文章中,_mm_set_ps翻譯成標舉的一個奇怪的序列,並解壓縮指令,讓我們看看:如何改進編譯器對我的SSE內部函數的處理?

int _tmain(int argc, _TCHAR* argv[]) 
{ 
    __m128 foo = _mm_set_ps(1.0f, 2.0f, 3.0f, 4.0f); 
    __m128 bar = _mm_set_ps(5.0f, 6.0f, 7.0f, 8.0f); 
    __m128 ret = _mm_add_ps(foo, bar); 

    // need to do something so vars won't be optimized out in Release 
    float *f = (float *)(&ret); 
    for (int i = 0; i < 4; i++) 
    { 
     cout << "f[" << i << "] = " << f[i] << endl; 
    } 
} 

接下來,我編譯和運行這個調試器裏面,看着拆卸:

調試:

__m128 foo = _mm_set_ps(1.0f,2.0f,3.0f,4.0f);
00B814F0 MOVAPS XMM0,xmmword PTR DS:[0B87840h]
00B814F7 MOVAPS xmmword PTR [EBP-190H],XMM0
00B814FE MOVAPS XMM0,xmmword PTR [EBP-190H]
00B81505 MOVAPS xmmword的ptr [富],XMM0
__m128 bar = _mm_set_ps(5.0f,6.0f,7.0f,8.0f);
00B81509 MOVAPS XMM0,xmmword PTR DS:[0B87850h]
00B81510 MOVAPS xmmword PTR [EBP-170H],XMM0
00B81517 MOVAPS XMM0,xmmword PTR [EBP-170H]
00B8151E MOVAPS xmmword的ptr [巴],XMM0
__m128 ret = _mm_add_ps(foo,bar);
00B81522 MOVAPS XMM0,xmmword的ptr [巴]
00B81526 MOVAPS xmm1中,xmmword的ptr [富]
00B8152A ADDPS xmm1中,XMM0
00B8152D MOVAPS xmmword PTR [EBP-150H],xmm1中
00B81534 MOVAPS XMM0,xmmword PTR [EBP-150H]
00B8153B MOVAPS xmmword的ptr [RET],XMM0

心亂如麻;爲什麼將xmmword放入__m128需要四個MOVAPS?首先,它將數據放入xmm0中(我認爲這是四個浮點值存儲在某處的文字,不知道如何查看它),然後將xmm0複製到某處由ebp指向的位置和一個偏移量,僅將其複製回來到xmm0(?),最後到應該存儲變量的位置。爲什麼這麼多工作?

發佈: 這一次,我期待編譯器可避免儲存xmmword在內存中的所有,只是把一個在XMM0,其他xmm1中,在內存中做一個ADDPS,把結果和用它做。相反,我得到:

__m128 foo = _mm_set_ps(1.0f,2.0f,3.0f,4.0f);
__m128 bar = _mm_set_ps(5.0f,6.0f,7.0f,8.0f);
__m128 ret = _mm_add_ps(foo,bar);
003E1009 MOVAPS XMM0,xmmword PTR DS:[3E2130h]
003E1010推ESI
003E1011 MOVAPS xmmword的ptr [ESP + 10H],XMM0

顯然,不需要ADDPS。我猜測編譯器注意到這兩個xmmwords是編譯時常量,所以它只是添加了它們,將結果作爲文字輸入到代碼中?奇怪的推動可能與隨後的for循環有關,因爲esi被用作循環計數器,據我所知。儘管如此,爲什麼不將數據段中的預先計算的文字放入xmm0,然後放入局部變量(esp + 10h),爲什麼不直接使用文字?總而言之,Debug版本比我預期的更愚蠢(或者我可能沒有收到什麼東西),而發佈版本卻意想不到。任何意見解釋這種行爲將不勝感激。謝謝。

編輯:的答案是很有啓發性,但我還是想知道如果有什麼可以做些什麼來改善編譯器的輸出,這就是爲什麼我從詢問到的這種解釋改變的問題目前的形式。

例如,纔有可能以某種方式引導編譯器酒吧不存儲在內存中(因爲我不添加後需要他們),只需將它們裝入xmmN寄存器,並讓他們那裏?可能ret呢?引用文章的作者說,MSVC只是「按照它所告訴的那樣做」。任何方式來改善(讀:避免內存傳輸)代碼,而不明確寫一個__asm塊?謝謝。

回答

4

這只是代碼生成器工作方式的正常副作用。 _mm_set_ps()有兩個不同的工作要做。它首先必須建立4個參數中的__m128值。你挑了最簡單的方式,它得到了很多更令人費解:

float x = 1.0f; 
__m128 foo = _mm_set_ps(x, 2.0f, 3.0f, 4.0f); 

有了顯着不同的代碼生成:

00C513DD movss  xmm0,dword ptr ds:[0C5585Ch] 
00C513E5 movss  xmm1,dword ptr [x] 
00C513EA movaps  xmm2,xmmword ptr ds:[0C55860h] 
00C513F1 unpcklps xmm0,xmm1 
00C513F4 unpcklps xmm2,xmm0 
00C513F7 movaps  xmmword ptr [ebp-100h],xmm2 

第二個作業然後將其移動到__m128變量,這很容易

00C513FE movaps  xmm0,xmmword ptr [ebp-100h] 
00C51405 movaps  xmmword ptr [foo],xmm0 

這還沒有優化,只是因爲在Debug版本中關閉了優化器。代碼生成器沒有做任何優化的嘗試,但這不是它的工作。

當然,優化器能夠在編譯時計算結果。這甚至適用於這個複雜的例子,你已經看到了這個:

00EE1284 movaps  xmm0,xmmword ptr ds:[0EE3260h] 
+0

一個非常好的解釋! – us2012 2013-02-14 14:33:53

+0

謝謝@Hans,您是否也會好好處理Q上的編輯?謝謝。 – neuviemeporte 2013-02-14 15:18:43

+0

要做任何事情來改善它都沒有意義。只需構建您的項目的發佈版本即可完成。在調試版本中打開優化器只會讓調試更加困難,調試版本的目的是讓它更容易*。如果你真的想,你可以幫助你瞭解它有多難。 – 2013-02-14 15:27:41

1

你對發佈版本的編譯時優化是正確的(在你的目標文件中查找ds:[3E2130h],你會在那裏找到附加的值)。

是,調試版本似乎做不必要的工作,但只有2倍,而不是一個4倍。有人會真正期待

movaps xmmword ptr [foo],xmmword ptr ds:[0B87840h] 

存在,但它沒有,MOVAPS有兩個變種,也不允許從內存移動到內存(這是x86的通常情況下):

MOVAPS xmm1,xmm2/mem128  ; 0F 28 /r  [KATMAI,SSE] 
MOVAPS xmm1/mem128,xmm2  ; 0F 29 /r  [KATMAI,SSE] 

什麼調試組裝並從ds:[0B87840h]在目標文件的.data部分閱讀xmmword(這是米可能只讀),並將其放在[ebp-190h]以及foo的堆棧中。

爲了比較,GCC 4.7表現出類似的模式:

movaps xmm0, XMMWORD PTR .LC0[rip] # D.5374, 
movaps XMMWORD PTR [rbp-64], xmm0 # foo, D.5353 
movaps xmm0, XMMWORD PTR .LC1[rip] # D.5381, 
movaps XMMWORD PTR [rbp-48], xmm0 # bar, D.5354 
movaps xmm0, XMMWORD PTR [rbp-64] # tmp79, foo 
movaps XMMWORD PTR [rbp-32], xmm0 # __A, tmp79 
movaps xmm0, XMMWORD PTR [rbp-48] # tmp80, bar 
movaps XMMWORD PTR [rbp-16], xmm0 # __B, tmp80 
movaps xmm0, XMMWORD PTR [rbp-16] # tmp81, __B 
movaps xmm1, XMMWORD PTR [rbp-32] # tmp82, __A 
addps xmm0, xmm1 # D.5386, tmp82 

我會假設,這與該內置內在的實現方式做。例如,_mm_add_ps__m128參數一起使用,該參數可能在寄存器,堆棧中或調用時的其他地方。因此,如果您正在爲gcc/VC++編寫內部代碼,則必須先生成將加載值的代碼。優化程序運行時,會立即發現無需推送數據(但優化程序不會在調試版本中運行)。

+0

wrt。到「人們會期待'movaps [mem],[mem]'存在......」 - 不。除了字符串移動('rep movs')以外,這種類型的指令(直接內存內存拷貝)在x86中不存在。 x86中的所有其他指令(包括所有SSE/AVX)只能擁有_a單一內存操作數。 – 2013-02-15 10:03:00

+0

@FrankH。這正是我所說的,如果你閱讀:「(這是x86中的常見情況)」。儘管如此,如果你對彙編程序還不熟悉,期望有* [mem],[mem]指令並不是不合理的,但它是混淆的常見來源。 – us2012 2013-02-15 10:23:35

1

這真的是一個關於MSVC內部的問題。要得到明確答案,你必須問問微軟。

有人可能會推測Release版本將ret放入局部變量的原因是您已經採用了它的地址。取一個變量的地址意味着編譯器突然不得不處理內存而不是寄存器。內存對於編譯器來說非常困難,因爲程序中的其他地方可能有指向優化器必須考慮的指針。

相關問題