2013-07-03 96 views
3

我希望能夠幫助優化程序中計算最密集的功能。 目前,我發現基本(非SSE)版本顯着更快(最多3倍)。因此,我會請求你幫忙糾正這一情況。優化SSE代碼

函數查找無符號整數向量中的子集,並報告它們是否存在。爲了您的方便,我只包含了相關的代碼片段。

首先是基本變體。它檢查block_是否是x.blocks_的一個子集。

//Check for self comparison 
    if (this == &x) 
      return false; 

//A subset is equal to or smaller. 
    if (no_bits_ > x.no_bits_) 
      return false; 

    int i; 

    bool equal = false; 

//Pointers should not change. 
    const unsigned int *tptr = blocks_; 
    const unsigned int *xptr = x.blocks_; 


    for (i = 0; i < no_blocks_; i++, tptr++, xptr++) { 
      if ((*tptr & *xptr) != *tptr) 
        return false; 
      if (*tptr != *xptr) 
        equal = true; 
    } 

    return equal; 

接着是上證所的變體,其中唉根據我的期望不執行。這兩個片段都應該尋找相同的東西。

//starting pointers.   
    const __m128i* start = (__m128i*)&blocks_; 
    const __m128i* xstart = (__m128i*)&x.blocks_; 

    __m128i block; 
    __m128i xblock; 

    //Unsigned ints are 32 bits, meaning 4 can fit in a register. 
    for (i = 0; i < no_blocks_; i+=4) { 

      block = _mm_load_si128(start + i); 
      xblock = _mm_load_si128(xstart + i); 

        //Equivalent to (block & xblock) != block 
        if (_mm_movemask_epi8(_mm_cmpeq_epi32(_mm_and_si128(block, xblock), block)) != 0xffff) 
          return false; 

        //Equivalent to block != xblock 
        if (_mm_movemask_epi8(_mm_cmpeq_epi32(block, xblock)) != 0xffff) 
          equal = true; 
    } 
    return equal; 

對於如何改進SSE版本的性能,您有什麼建議嗎?難道我做錯了什麼?或者,這是否應該在其他地方進行優化?

我還沒有在剩餘的計算中加入no_blocks_%4!= 0,但是這樣做的目的很小,直到性能提高爲止,並且它只會使代碼在這一點上混亂起來。

謝謝你提供的任何幫助。

+0

您是否真的瞭解了編譯器爲兩種情況生成的內容。我發現GCC/G ++首先生成相當不錯的SSE代碼(並且它更好地避免了我們人類傾向於提出的「寄存器依賴性」)。 –

+0

http://channel9.msdn.com/Events/Build/2013/4-329 –

回答

3

我在這裏看到了三種可能性。

首先,您的數據可能不適合廣泛的比較。如果(*tptr & *xptr) != *tptr在前幾個塊內有很高的可能性,那麼簡單的C++版本幾乎肯定會更快。在這種情況下,您的上證所將運行更多的代碼&數據來完成相同的事情。

其次,您的SSE代碼可能不正確。這裏並不完全清楚。如果no_blocks_在兩個樣本之間相同,則start + i可能具有索引128位元素的不良行爲,而不是32位作爲第一個樣本。

三,SSE 真的喜歡它,當指令可以流水線,這是一個很短的循環,你可能沒有得到。您可以通過一次處理多個SSE塊來顯着減少分支。

這是一次處理2個SSE區塊的快速未經測試的鏡頭。注意我已經完全刪除了block != xblock分支,將狀態保持在循環之外,並且只在最後進行測試。總的來說,這會將事件從每個int的1.3個分支移動到0.25。

bool equal(unsigned const *a, unsigned const *b, unsigned count) 
{ 
    __m128i eq1 = _mm_setzero_si128(); 
    __m128i eq2 = _mm_setzero_si128(); 

    for (unsigned i = 0; i != count; i += 8) 
    { 
     __m128i xa1 = _mm_load_si128((__m128i const*)(a + i)); 
     __m128i xb1 = _mm_load_si128((__m128i const*)(b + i)); 

     eq1 = _mm_or_si128(eq1, _mm_xor_si128(xa1, xb1)); 
     xa1 = _mm_cmpeq_epi32(xa1, _mm_and_si128(xa1, xb1)); 

     __m128i xa2 = _mm_load_si128((__m128i const*)(a + i + 4)); 
     __m128i xb2 = _mm_load_si128((__m128i const*)(b + i + 4)); 

     eq2 = _mm_or_si128(eq2, _mm_xor_si128(xa2, xb2)); 
     xa2 = _mm_cmpeq_epi32(xa2, _mm_and_si128(xa2, xb2)); 

     if (_mm_movemask_epi8(_mm_packs_epi32(xa1, xa2)) != 0xFFFF) 
      return false; 
    } 

    return _mm_movemask_epi8(_mm_or_si128(eq1, eq2)) != 0; 
} 

如果你有足夠的數據和失敗的前幾個SSE塊中的概率很低,這樣的事情應該是至少一定程度上比你快SSE。

+0

我已經嘗試了兩種解決方案。不幸的是,它似乎沒有加快速度,而是需要更長的時間。然而,修正索引確實有所幫助。儘管如此,感謝您的時間和精力,因爲我已經從這個更聰明的人中脫穎而出,希望更好地提高分支意識。 – Dess

+0

你要喂什麼數據?我開始懷疑你真的很早就擺脫了這個循環。 –

+0

是的,事實證明是這樣。在檢查迭代之後,大多數調用完成循環。但是,我之前沒有測試過;那些完成了循環的人,絕大多數都很小(在'no_blocks_ <= 16'處檢查)。作爲迴應,我將嘗試執行更大的向量或將向量移動到靜態大小,然後使用向量,我希望能夠以智能方式進行管道傳輸。 – Dess

0

我似乎認爲你的問題是一個內存帶寬有界的問題: 漸近你需要大約2個操作來處​​理掃描的內存中的一對整數。沒有足夠的算術複雜度來利用CPU SSE指令使用更多的算術吞吐量。事實上,你的CPU通過大量的時間來等待數據傳輸。 但是,在你的情況下使用SSE指令會導致整個指令,並且編譯器不會對生成的代碼進行優化。

有一些替代戰略,改善帶寬約束問題表現爲:通過並行運算

  • 多線程隱藏存取存儲器超線程情況下 操作。
  • 數據加載大小的精細調整可以提高內存帶寬。
  • 通過在循環中添加補充獨立操作來提高管道連續性(在「for」循環中的每個步驟掃描兩組不同的數據)
  • 將更多數據保存在緩存或寄存器中(某些迭代代碼可能需要多次相同的數據集)