Corei7アーキテクチャーでdouble/integersのベクトルの最小/最大の計算を高速化できるasm命令はありますか?
アップデート:
こんなに豊富な答えは期待していませんでした、ありがとうございます。したがって、最大/最小は分岐せずに実行できることがわかります。サブ質問があります:
配列内の最大のdoubleのインデックスを取得する効率的な方法はありますか?
Corei7アーキテクチャーでdouble/integersのベクトルの最小/最大の計算を高速化できるasm命令はありますか?
アップデート:
こんなに豊富な答えは期待していませんでした、ありがとうございます。したがって、最大/最小は分岐せずに実行できることがわかります。サブ質問があります:
配列内の最大のdoubleのインデックスを取得する効率的な方法はありますか?
SSE4にはPMAXSD
、またはPMAXUD
32ビットの符号付き/符号なし整数があります。これは便利な場合があります。
SSE2にはMAXPD
、MAXSD
doubleのペア間で比較されるため、1つのMAXSDでn / 2-1 MAXPDを追跡し、通常の負荷と操作のインターレースを使用して、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の配列の最小値と最大値を100,000回2倍に計算します。
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;
}
パート2に対応して、max演算から分岐を削除する従来の最適化は、値を比較し、フラグを1ビット(0または1を与える)として取得し、1を減算(0または0xffff_ffffを与える)し、'および' 2つの可能な結果のxorであるため、。と同等の結果が得られます( a > best ? ( current_index ^ best_index ) : 0 ) ^ best_index )
。SSEはタグ付きの値ではなくパックされた値を操作する傾向があるため、これを行う簡単なSSEの方法があるとは思えません。いくつかの水平インデックス操作があるので、最大値を見つけて、元のベクトルのすべての要素からそれを減算し、符号ビットを収集すると、ゼロ符号付きのものが最大値のインデックスに対応しますが、おそらくそれはショートまたはバイトを使用していない限り、改善にはなりません。
SSEのMAXPSとMINPSはどちらも、パックされた単精度浮動小数点数で動作します。PMAXSW、PMINSW、PMAXUB、およびPMINUBはすべて、符号付きまたは符号なしのパックされた8ビットワードで動作します。これらは2つの入力SSEレジスタまたはアドレス位置を要素ごとに比較し、結果をSSEレジスタまたはメモリ位置に格納することに注意してください。
MAXPSおよびMINPSのSSE2バージョンは、倍精度浮動小数点数で動作するはずです。
どのコンパイラと最適化フラグを使用していますか?gcc 4.0以降では、ターゲットが操作をサポートしている場合、操作を自動的にベクトル化する必要があります。以前のバージョンでは、特定のフラグが必要になる場合があります。
2番目の質問への回答:ほとんどのプラットフォームには、この操作(および他のほとんどの単純なベクトル操作)の最適化された実装がすでに含まれているライブラリがあります。 それらを使用してください。
vDSP_maxviD( )
、cblas_idamax( )
Accelerate.frameworkがあります。cblas_idamax( )
cblas_idamax( )
BLASライブラリに含まれていますが、その出所に応じて適切に調整されている場合とされていない場合があります。パフォーマンスを気にするユーザーは、一般的に優れた実装を持っています(またはそれをインストールするように説得することができます)更新:パート2で「ベクトル」ではなく「配列」と言っていることに気づきました。これは、役立つ場合に備えて、とにかくここに残しておきます。
re:パート2:SSEベクトルのmax / min要素のインデックスを見つけます:
水平方向の最大値を実行します。double
2要素の128bベクトルの場合、結果を両方の要素にブロードキャストするための1つのshufpd
+です。maxpd
その他の場合は、もちろん、より多くの手順を実行します。アイデアについては、 x86で水平フロートベクトルの合計を実行する最速の方法を参照してください。またはに置き換えaddps
てください。(ただし、SSE4を使用できるため、16ビット整数は特別であることに注意してください。最大の場合は、255から減算します)maxps
minps
phminposuw
ベクトルの元のベクトルとすべての要素が最大であるベクトルをパック比較します。
(pcmpeqq
整数ビットパターンまたは通常のcmpeqpd
両方がこのdouble
場合に機能します)。
int _mm_movemask_pd (__m128d a)
(movmskpd
)比較結果を整数ビットマップとして取得します。bsf
(最初の)一致のためにビットスキャン( )します: index = _bit_scan_forward(cmpmask)
。整数比較を使用した場合、cmpmask = 0は不可能です(少なくとも1つの要素がNaNであっても一致するため)。これは、6つの命令(を含むmovapd
)にのみコンパイルする必要があります。うん、Godboltコンパイラエクスプローラーをチェックしたところ、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
NaN入力と可換ではないことに注意してください。NaNが可能で、Intel Nehalemのパフォーマンスを気にしない場合は、_mm_cmpeq_epi64
ビットパターンの比較に使用することを検討してください。ただし、floatからvec-intへのバイパス遅延はNehalemの問題です。
NaN!= IEEE浮動小数点のNaNであるため_mm_cmpeq_pd
、all-NaNの場合、結果マスクはすべてゼロになる可能性があります。
2要素の場合に常に0または1を取得するために実行できる別のことは、ビットスキャンを。に置き換えることですcmpmask >> 1
。(bsf
input = all-zeroでは奇妙です)。
2番目の質問に答えて、このデータを収集および保存する方法について考えることは価値があるかもしれません。
データを常にソートされた状態に保つBツリーにデータを格納し、対数比較操作のみを必要とする場合があります。
そうすれば、いつでも最大値がどこにあるかがわかります。