2013-02-10 175 views
3

我想比較兩行pixel s。爲什麼這比memcmp慢

A pixel被定義爲包含4個值(RGBA)的struct

我不使用memcmp的原因是因爲我需要返回第一個不同像素的位置,而不是memcmp

我的第一個實現使用SSE內部函數,並且是〜比memcmp慢30%:

inline int PixelMemCmp(const Pixel* a, const Pixel* b, int count) 
{ 
    for (int i = 0; i < count; i++) 
    { 
     __m128 x = _mm_load_ps((float*)(a + i)); 
     __m128 y = _mm_load_ps((float*)(b + i)); 
     __m128 cmp = _mm_cmpeq_ps(x, y); 
     if (_mm_movemask_ps(cmp) != 15) return i; 
    } 
    return -1; 
} 

然後我發現,治療的價值觀爲整數,而不是浮點數加快了一點東西,只有現在〜比memcmp慢20%。

inline int PixelMemCmp(const Pixel* a, const Pixel* b, int count) 
{ 
    for (int i = 0; i < count; i++) 
    { 
     __m128i x = _mm_load_si128((__m128i*)(a + i)); 
     __m128i y = _mm_load_si128((__m128i*)(b + i)); 
     __m128i cmp = _mm_cmpeq_epi32(x, y); 
     if (_mm_movemask_epi8(cmp) != 0xffff) return i; 
    } 
    return -1; 
} 

從我讀過關於其他問題的memcmp的MS執行使用SSE也可以實現。我的問題是MS實施有什麼其他的技巧,我不知道它的袖子?即使它進行逐字節比較,它又如何更快?

對齊是一個問題嗎?如果pixel包含4個浮點數,那麼在16字節邊界上是否已經分配了一個像素數組?

我在編譯/o2和所有的優化標誌。

+2

不,不能保證它會在16個字節上對齊,除非您親自處理它。 – interjay 2013-02-10 10:14:08

+0

是的,對齊是一個問題。您使用的編譯選項也是相關的。你也應該顯示生成的asm。也許編譯器缺少循環展開或代碼上的其他優化。 – 2013-02-10 10:18:59

回答

3

我寫的strcmp/memcmp優化與SSE(!和MMX /支持3DNow),而第一步就是要確保陣列儘可能一致 - 你會發現,你所要做的第一和/或最後字節「一次一個」。

如果您可以在數據進入循環之前對齊數據[如果您的代碼執行了分配],那麼這很理想。

第二部分是展開循環,所以你不會得到那麼多的「如果循環不在最後,跳回循環開始」 - 假設循環很長。

您可能會發現,在執行「我們現在離開」條件之前預加載下一個輸入數據也會有所幫助。

編輯:最後一段可能需要一個例子。此代碼假定至少有兩個展開循環:

__m128i x = _mm_load_si128((__m128i*)(a)); 
__m128i y = _mm_load_si128((__m128i*)(b)); 

for(int i = 0; i < count; i+=2) 
{ 
    __m128i cmp = _mm_cmpeq_epi32(x, y); 

    __m128i x1 = _mm_load_si128((__m128i*)(a + i + 1)); 
    __m128i y1 = _mm_load_si128((__m128i*)(b + i + 1)); 

    if (_mm_movemask_epi8(cmp) != 0xffff) return i; 
    cmp = _mm_cmpeq_epi32(x1, y1); 
    __m128i x = _mm_load_si128((__m128i*)(a + i + 2)); 
    __m128i y = _mm_load_si128((__m128i*)(b + i + 2)); 
    if (_mm_movemask_epi8(cmp) != 0xffff) return i + 1; 
} 

大致類似的東西。

+1

展開取得了所有的不同!我展開了4倍,比'memcmp'慢20%,比'memcmp'快20%。出於某種原因,對齊似乎完全沒有區別('malloc' vs'_aligned_malloc(16)')。你能解釋一下你的最後一段嗎?我不懂你什麼意思。 – Rotem 2013-02-10 12:28:30

+1

如果你的輸入數組還沒有被16字節對齊,那麼你會遇到崩潰,因爲你正在使用加載函數的對齊版本(例如'_mm_load_si128()'與'_mm_loadu_si128()'。如果你想對可能未對齊的輸入進行強健的處理,然後您可以使用未對齊的加載函數,但即使數組對齊,也會有輕微的性能下降。 – 2013-02-10 15:04:14

0

因爲我使用的是Mac,我不能幫你直接,但有一個簡單的方法來弄清發生了什麼:

你只要踏入的memcpy在調試模式,並切換到反彙編視圖。由於memcpy是一個簡單的小函數,因此您可以輕鬆找出所有實現技巧。

3

你可能要檢查這個memcmp SSE implementation,特別是__sse_memcmp功能,它與一些理智的檢查開始,然後如果指針對準或沒有檢查:

aligned_a = ((unsigned long)a & (sizeof(__m128i)-1)); 
aligned_b = ((unsigned long)b & (sizeof(__m128i)-1)); 

如果他們對準它進行比較指針逐字節直到對齊地址的開始:

while(len && ((unsigned long) a & (sizeof(__m128i)-1))) 
{ 
    if(*a++ != *b++) return -1; 
    --len; 
} 

然後比較類似於你的代碼的SSE指令的剩餘內存:

if(!len) return 0; 
while(len && !(len & 7)) 
{ 
__m128i x = _mm_load_si128((__m128i*)&a[i]); 
__m128i y = _mm_load_si128((__m128i*)&b[i]); 
.... 
+0

謝謝,代碼在某種程度上是有用的,儘管我正在處理可以預先對齊的數據,所以所有的理智檢查和尾部邏輯都與我的案例無關。 – Rotem 2013-02-10 12:34:22

+0

@Rotem如果你可以對齊數據,那麼你並不真的需要這些,你有沒有使用對齊測試代碼? – iabdalkader 2013-02-10 12:43:25

+0

是的,結果是相同的(通過'_aligned_malloc'對齊)。我不知道如何解釋這個事實。 – Rotem 2013-02-10 12:45:55