u64 ループ カウンタと x86 CPU 上の _mm_popcnt_u64 の間の異常なパフォーマンスの違いを探る
はじめに
大規模なデータ配列に対する操作を簡単に実行する方法を探していますPopcount メソッドを実行すると、非常に奇妙な動作が発生しました。ループ変数を unsigned から uint64_t に変更すると、PC のパフォーマンスが 50% 低下しました。
ベンチマーク
#include <iostream> #include <chrono> #include <x86intrin.h> int main(int argc, char* argv[]) { using namespace std; if (argc != 2) { cerr (buffer); for (unsigned i=0; i<size charbuffer rand uint64_t count chrono::time_point> startP,endP; { startP = chrono::system_clock::now(); count = 0; for( unsigned k = 0; k (endP-startP).count(); cout (endP-startP).count(); cout <p> ご覧のとおり、サイズ x MB のランダム データ バッファーを作成しました。ここで、x はコマンド ラインから読み取られます。次に、バッファーを反復処理し、x86 ポップカウント組み込み関数のアンロール バージョンを使用してポップカウントを実行します。より正確な結果を得るために、ポップカウントを 10,000 回実行します。ポップカウントを測定する時間。最初のケースでは、内部ループ変数は符号なしであり、2 番目のケースでは、内部ループ変数は uint64_t です。これでは何も変わらないはずだと思っていましたが、そうではありませんでした。 </p> <p><strong> (絶対にクレイジー) 結果 </strong></p> <p>次のようにコンパイルしました (G バージョン: Ubuntu 4.8.2-19ubuntu1): </p> <pre class="brush:php;toolbar:false">g++ -O3 -march=native -std=c++11 test.cpp -o test
これHaswell Core i7-4770K CPU @ 3.50GHz でテストを実行しました。 1 の結果 (つまり 1MB のランダム データ):
ご覧のとおり、uint64_t バージョンのスループットは署名なしバージョンの半分です。問題は、異なるアセンブリが生成されることのようですが、理由は何でしょうか?まず、コンパイラのバグだと思ったので、clang (Ubuntu Clang バージョン 3.4-1ubuntu3) を試してみました:
clang++ -O3 -march=native -std=c++11 teest.cpp -o test
テスト結果 1:
つまり、ほぼ同じ結果が得られますが、それでも奇妙です。しかし今では本当に奇妙になってしまいました。入力から読み取られたバッファ サイズを定数 1 に置き換えたので、
uint64_t size = atol(argv[1]) <p> から </p><pre class="brush:php;toolbar:false">uint64_t size = 1 <p> に変更しました。これにより、コンパイラはコンパイル時にバッファ サイズを認識できるようになりました。もしかしたら、いくつかの最適化を追加できるかもしれません。 g 単位の数値は次のとおりです: </p>
どちらのバージョンも同等に高速になりました。ただし、velocidade は署名なしと比較してさらに遅くなります。 26 GB/秒から 20 GB/秒に低下したため、型破りな定数を定数値に置き換えると非最適化が発生しました。真剣に、ここでは手がかりがありません!しかし、Clang と新しいバージョンでは、
uint64_t size = atol(argv[1]) <p> が </p><pre class="brush:php;toolbar:false">uint64_t size = 1 <p> に変更されました。 結果: </p>
待て、何が起こった?現在、どちらのバージョンも 15GB/s の低速速度に低下しています。そのため、型破りな定数値を定数値に置き換えると、Clang のコードの 2 つ のバージョンが遅くなることさえありました。
Ivy Bridge CPU を使用している同僚にベンチマークをコンパイルするよう依頼しました。彼も同様の結果を得たので、これはハスウェルに特有のものではないようです。ここでは 2 つのコンパイラが奇妙な結果を生成するため、これもコンパイラのバグではないようです。ここには AMD CPU がないため、テストには Intel のみを使用できます。
もっとクレイジーにしてください!
最初の例 (atol(argv[1]) を使用した例) を使用して、変数の前に static を置きます。つまり、
#include <iostream> #include <chrono> #include <x86intrin.h> int main(int argc, char* argv[]) { using namespace std; if (argc != 2) { cerr (buffer); for (unsigned i=0; i<size charbuffer rand uint64_t count chrono::time_point> startP,endP; { startP = chrono::system_clock::now(); count = 0; for( unsigned k = 0; k (endP-startP).count(); cout (endP-startP).count(); cout <p>これが彼女の内容です。結果は g: </p> <ul> <li>unsigned 41959360000 0.396728 秒 26.4306 GB/秒 </li> <li>uint64_t 41959360000 0.509484 秒 20.5811 GB/秒 </li> </ul> <p>はい、別の代替手段もあります。 u3 ではまだ 32GB/s ですが、u64 では少なくとも 13GB/s バージョンから 20GB/s バージョンに到達することができました。同僚のコンピュータでは、u64 バージョンの方が u32 バージョンよりもさらに高速で、最良の結果が得られました。残念ながら、これは g でのみ機能し、clang は静的を気にしていないようです。 </p> <p>**私の質問</p></size></x86intrin.h></chrono></iostream>
以上がループ カウンタを「unsigned」から「uint64_t」に変更すると、x86 CPU 上の「_mm_popcnt_u64」のパフォーマンスに大きな影響を与えるのはなぜですか。また、コンパイラの最適化と変数宣言はどのように影響しますか?の詳細内容です。詳細については、PHP 中国語 Web サイトの他の関連記事を参照してください。