2009-12-28 85 views
7

是否有任何可以加速Core i7架構上雙/整數矢量最小/最大值計算的asm指令?x86最大/最小asm指令?

更新:

我沒想到會這麼豐富的解答,謝謝。 所以我看到最大/最小值可能沒有分支。 我有子問題:

有沒有一種有效的方法來獲得最大的雙數的索引?

+0

什麼是宿主語言?如果它是c/C++,我不會擔心它太多。 – 2009-12-28 14:48:17

+0

最大約300個雙打是大型項目的最內層循環。在8'000行代碼中,大約有10%花費了85%的時間。主機語言並不重要,正因爲如此。但是,它是C++ – 2009-12-28 14:51:41

回答

12

對於32位有符號/無符號整數,SSE4具有PMAXSDPMAXUD,這可能很有用。

SSE2具有MAXPDMAXSD其中比較和跨地區對雙打的,所以你按照N/2-1 MAXPDs一個MAXSD得到n的向量的最大值,與負載和操作的通常交錯。

有以上MIN等值。

對於雙的情況下,你可能不會做的更好彙編比SSE模式半像樣的C++編譯器:

peregrino:$ g++ -O3 src/min_max.cpp -o bin/min_max 
peregrino:$ g++ -O3 -msse4 -mfpmath=sse src/min_max.cpp -o bin/min_max_sse 
peregrino:$ time bin/min_max 
0,40 

real 0m0.874s 
user 0m0.796s 
sys 0m0.004s 
peregrino:$ time bin/min_max_sse 
0,40 

real 0m0.457s 
user 0m0.404s 
sys 0m0.000s 

其中min_max計算的500個雙打陣列的最小值和最大值用天真的循環10萬次:

bool min_max (double array[], size_t len, double& min, double& max) 
{ 
    double min_value = array [ 0 ]; 
    double max_value = array [ 0 ]; 

    for (size_t index = 1; index < len; ++index) { 
     if (array [ index ] < min_value) min_value = array [ index ]; 
     if (array [ index ] > max_value) max_value = array [ index ]; 
    } 

    min = min_value; 
    max = max_value; 
} 

針對兩部分,傳統的優化刪除從最大操作分支是比較值,獲得標誌作爲一個唱(比如給出0或1),減去1(給出0或0xffff_ffff),'和'與兩個可能結果的異或,所以你得到相當於(a > best ? (current_index^best_index) : 0)^best_index)。我懷疑有一種簡單的SSE方式來做到這一點,只是因爲SSE傾向於使用壓縮值而不是標記值;有一些水平索引操作,所以你可以嘗試找到最大值,然後從原始向量中的所有元素中減去該值,然後收集符號位,並且簽名的零將對應於最大值的索引,但這可能會除非您使用短褲或字節,否則不會有所改進。

+0

您只需要log2(vector_length)shuffle + MAXPS/MAXPD操作(而不是VL/2)來獲取單個SIMD向量的水平最大值。這與[水平總和]基本上是一樣的想法(https://stackoverflow.com/questions/6996764/fastest-way-to-do-horizo​​ntal-float-vector-sum-on-x86):每次縮小一半。 (或將結果廣播到每個元素,交換高/低)。 – 2017-08-07 08:03:31

+0

如果你不是內存瓶頸,使用多個累加器展開應該會提供比2x更好的速度。 ('MAXPD'有3或4個週期的延遲,但每個週期的吞吐量爲1,所以你需要編譯器發出使用多個向量的asm,並將它們結合到數組末尾。)clang往往會這樣做,矢量化,但gcc通常不會。 – 2017-08-07 08:06:47

4

來自SSE的MAXPS和MINPS都對打包的單精度浮點數進行操作。 PMAXSW,PMINSW,PMAXUB和PMINUB均可對包裝的8位字進行操作,無論是有符號還是無符號。請注意,這些比較兩個輸入SSE寄存器或地址位置元素明智並將結果存儲到一個SSE寄存器或內存位置。

MAXPS和MINPS的SSE2版本應該可以在雙精度浮點上工作。

您使用哪種編譯器和優化標誌?如果您的目標支持它們,gcc 4.0和更高版本應自動矢量化操作,而早期版本可能需要特定的標誌。

2

,如果您使用的是英特爾的IPP庫,你可以使用矢量statistical functions計算矢量最小/最大(除其他事項外)

2

在回答你的第二個問題:在大多數平臺上,有一些已經包含優化庫這個操作的實現(以及大多數其他簡單的向量操作)。 使用它們

  • 在OS X上,存在vDSP_maxviD()cblas_idamax()的Accelerate.framework
  • 英特爾編譯器包括IPP和MKL庫,具有高性能的實現,包括cblas_idamax()
  • 大多數Linux系統將有cblas_idamax()在BLAS圖書館中,根據其出處可能調整或可能不調整;關心性能的用戶通常會有很好的實現(或者可以被說服去安裝一個)
  • 如果一切都失敗了,你可以使用ATLAS(自動調優線性代數軟件)在目標平臺
  • 上獲得不錯的性能實現
-1

對於您的第二個問題,您可能需要考慮收集和存儲這些數據的方式。

您可以將數據存儲在保持數據始終排序的B樹中,只需要進行對數比較操作。

然後你總是知道最大值是多少。

http://en.wikipedia.org/wiki/B_tree

+1

既然你只處理300個雙打,自平衡二叉樹可能是最好的。 http://en.wikipedia.org/wiki/Self-balancing_binary_search_tree – Drew 2012-02-16 03:29:01

+0

爲什麼不是二進制堆?恆定的時間比對數更好... – 2014-04-13 20:34:59

0

更新:我只是意識到,你說在第2部分「陣列」,而不是「矢量」我會在這裏反正如果離開這非常有用。


重新:兩部分:找到最大/最小元件的在SSE矢量的索引:

  • 做一個水平最大。對於2個double元素的128b向量,這只是一個shufpd + maxpd將結果廣播到這兩個元素。

    對於其他情況,它當然會採取更多步驟。有關想法,請參閱Fastest way to do horizontal float vector sum on x86,將addps替換爲maxpsminps。 (但請注意,16位整數是特殊的,因爲你可以使用SSE4 phminposuw。對於最大,從255減去)

  • 執行矢量原始載體,每一個元素是最大的載體之間的填充比較。

    pcmpeqq整數位模式或通常cmpeqpd都將爲double情況下工作)。

  • int _mm_movemask_pd (__m128d a) (movmskpd)以比較結果作爲整數位圖。
  • 位掃描(bsf)它用於(第一次)匹配:index = _bit_scan_forward(cmpmask)。如果使用整數比較,則cmpmask = 0是不可能的(因爲即使它們是NaN,至少一個元素也會匹配)。

這應該編譯成只有6條指令(包括一個movapd)。是的,剛剛檢查the Godbolt compiler explorer,它確實與SSE。

#include <immintrin.h> 
#include <x86intrin.h> 

int maxpos(__m128d v) { 
    __m128d swapped = _mm_shuffle_pd(v,v, 1); 
    __m128d maxbcast = _mm_max_pd(swapped, v); 
    __m128d cmp = _mm_cmpeq_pd(maxbcast, v); 
    int cmpmask = _mm_movemask_pd(cmp); 
    return _bit_scan_forward(cmpmask); 
} 

請注意,_mm_max_pd is not commutative with NaN inputs。如果NaN可能,並且您不關心Intel Nehalem的性能,則可以考慮使用_mm_cmpeq_epi64來比較位模式。儘管如此,從float到vec-int的旁路延遲在Nehalem上是一個問題。

NaN!= NaN在IEEE浮點,因此_mm_cmpeq_pd結果掩碼可能在全NaN情況下全部爲零。

您可以在2元素的情況下始終得到0或1的另一件事是用cmpmask >> 1替換位掃描。 (bsf奇怪,輸入=全零)。