2011-02-15 94 views
11

我想找出一個有效的方法來加載編譯時間常量浮點到SSE(2/3)寄存器。我試過這樣簡單的代碼,加載常量浮點到SSE寄存器

const __m128 x = { 1.0f, 2.0f, 3.0f, 4.0f }; 

但是,它會從內存中產生4條movss指令!

movss  xmm0,dword ptr [[email protected] (14048E534h)] 
movss  xmm1,dword ptr [[email protected] (14048E530h)] 
movaps  xmm6,xmm12 
shufps  xmm6,xmm12,0C6h 
movss  dword ptr [rsp],xmm0 
movss  xmm0,dword ptr [[email protected] (14048E52Ch)] 
movss  dword ptr [rsp+4],xmm1 
movss  xmm1,dword ptr [[email protected] (14048E528h)] 

其裝入標量和流出的記憶......(?!?!)

這樣做雖然..

float Align(16) myfloat4[4] = { 1.0f, 2.0f, 3.0f, 4.0f, }; // out in global scope 

產生。

movaps  xmm5,xmmword ptr [::myarray4 (140512050h)] 

理想的情況下,這將是很好,如果我有常數他們將是一個辦法不連碰內存,只是立即樣式指令做(例如編入指令本身的常量)。

由於

+2

對於高性能的SSE/2代碼,我強烈建議使用GCC/ICC。閱讀此更多信息爲什麼 - http://www.liranuna.com/sse-intrinsics-optimizations-in-popular-compilers/ – LiraNuna 2011-03-07 21:43:54

回答

6

如果您想強制執行單個加載,可以嘗試(gcc):

__attribute__((aligned(16))) float vec[4] = { 1.0f, 1.1f, 1.2f, 1.3f }; 
__m128 v = _mm_load_ps(vec); // edit by sor: removed the "&" cause its already an address 

如果您有Visual C++,請使用__declspec(align(16))要求適當的約束。

在我的系統,這個(編譯gcc -m32 -msse -O2;在所有雜波的代碼,但仍保留在最後的單movaps沒有優化)創建以下彙編代碼(GCC/AT & T語法):

andl $-16, %esp 
    subl $16, %esp 
    movl $0x3f800000, (%esp) 
    movl $0x3f8ccccd, 4(%esp) 
    movl $0x3f99999a, 8(%esp) 
    movl $0x3fa66666, 12(%esp) 
    movaps (%esp), %xmm0 

請注意,它在分配堆棧空間並將常量放在那裏之前對齊堆棧指針。根據你的編譯器的不同,離開__attribute__((aligned))可能會產生不正確的代碼,因此請注意並檢查反彙編。

此外:
既然你已經要求如何把常量到代碼,只是嘗試上面有static限定符float陣列。這將創建以下程序集:

movaps vec.7330, %xmm0 
    ... 
vec.7330: 
    .long 1065353216 
    .long 1066192077 
    .long 1067030938 
    .long 1067869798 
2

通常常量如這將任何環或代碼的「熱」部件之前被加載,所以性能不應該是很重要的。但是如果你不能避免在循環中做這種事情,那麼我會先試着_mm_set_ps,看看會產生什麼。也可以嘗試ICC而不是gcc,因爲它傾向於生成更好的代碼。

+0

我正在使用visual studio和_mm_set_ps正在生成更多movss。我認爲visual studio編譯器非常糟糕。 – coderdave 2011-02-15 19:49:06

+2

@coderdave:是的Visual Studio生成非常糟糕的SSE代碼 - 這也是一個痛苦的SSE使用,因爲它有各種愚蠢的ABI限制和其他煩惱 - 使用海灣合作委員會或更好的ICC,如果你可以 – 2011-02-15 20:29:19

3

首先,你在編譯什麼優化級別?在-O0或-O1上看到這種代碼的情況並不少見,但是在大多數編譯器中看到-O2或更高的代碼,我會很驚訝。

其次,SSE沒有立即加載。你可以做一個負載立即到GPR,則該值轉移到SSE,但你不能沒有實際負載召喚其他值(忽略像0(int)-1,可通過邏輯運算產生某些特殊的價值。

最後,如果開啓優化並且在性能關鍵位置生成錯誤代碼,請針對您的編譯器提交一個錯誤。

2

如果四個浮點常量相同,則生成常量要簡單得多(而且更快)。例如,1.f的位模式是0x3f800000。其中一種方法可以使用SSE2

 register __m128i onef; 
     __asm__ ("pcmpeqb %0, %0" : "=x" (onef)); 
     onef = _mm_slli_epi32(onef, 25); 
     onef = _mm_srli_epi32(onef, 2); 

與SSE4.1另一種方法是,

 register uint32_t t = 0x3f800000; 
     register __m128 onef; 
     __asm__ ("pinsrd %0, %1, 0" : "=x" (onef) : "r" (t)); 
     onef = _mm_shuffle_epi32(onef, 0); 

請注意,我沒有獨到之處,如果這個版本是任何比SSE2一個更快的生成,還沒有描繪它,只測試結果是正確的。

如果每個浮點數的值必須不同,那麼每個常量都可以被生成並混洗或混合在一起。

這是否有用取決於緩存未命中是否可能,否則從內存加載常量更快。像這樣的技巧在vmx/altivec中非常有用,但大多數pcs上的大型緩存可能會使這個對sse不太有用。

Agner Fog's Optimization Manual,book 2,section 13.4,http://www.agner.org/optimize/有很好的討論。

最後需要注意的是,上面的內聯彙編程序的使用是gcc特有的,原因是允許使用未初始化的變量而不產生編譯器警告。使用vc,您可能需要也可能不需要首先使用_mm_setzero_ps()初始化變量,然後希望優化器可以刪除它。