簡體   English   中英

如何改進編譯器對SSE內在函數的處理?

[英]How can I improve the compiler's handling of my SSE intrinsics?

在閱讀了這篇關於SSE代碼在不同C ++編譯器中內部引導優化結果的有趣文章后,我決定對自己進行測試,特別是因為帖子已有幾年了。 我使用的MSVC在帖子的作者(雖然在VS 2010版本中)執行的測試中表現非常糟糕,並且決定堅持一個非常基本的場景:將一些值打包到XMM寄存器中並進行簡單的操作,如加法。 在文章中,_mm_set_ps翻譯成一個奇怪的標量移動和解包指令序列,所以讓我們看看:

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 [foo],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 [bar],xmm0
__m128 ret = _mm_add_ps(foo,bar);
00B81522 movaps xmm0,xmmword ptr [bar]
00B81526 movaps xmm1,xmmword ptr [foo]
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版本比我預期的更愚蠢(或者我可能沒有得到什么),而Release版本出乎意料地聰明。 任何解釋此行為的評論將不勝感激。 謝謝。

編輯:答案是非常有啟發性的,但我仍然想知道我是否可以做任何事情來改善編譯器輸出,這就是為什么我要求將此問題解釋為當前形式的問題。

例如,是否有可能以某種方式引導編譯器不將foobar存儲在內存中(因為我在添加后不需要它們),只需將它們加載到xmmN寄存器並保存在那里? 可能也會退回 引用文章的作者說,MSVC只是“完全按照它所說的去做”。 有沒有明確寫出__asm塊的任何方法可以更好(讀取:避免內存傳輸)代碼? 謝謝。

這只是代碼生成器工作方式的正常副作用。 _mm_set_ps()有兩個不同的工作要做。 它首先必須從4個參數中建立__m128值。 你選擇了簡單的方法,它變得更加復雜:

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

具有完全不同的codegen:

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]  

您對發布版本的編譯時優化是正確的(在目標文件中查找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]

調試程序集執行的操作是從目標文件的.data部分(最有可能是readonly)讀取ds:[0B87840h]中的ds:[0B87840h] ,並將其放在[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參數一起使用, __m128參數可能在寄存器中,堆棧上或調用它時的其他位置。 因此,如果您正在為gcc / VC ++編寫內在函數代碼,則必須首先生成將加載值的代碼。 當優化器運行時,它會立即注意到存在不必要的數據推送(但優化器不會在調試版本中運行)。

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

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

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM