質問: 1 << 20; したがって、コンパイラはコンパイル時にバッファサイズを認識します。多分それはいくつかの最適化を追加することができます!これがg++の番号です。 符号なし41959360000 0.509156秒 20.5944 gb /秒 uint64_t 41959360000 0.508673秒 20.6139 gb /秒 今、両方のバージョンは同じくらい速いです。しかし、unsigned はさらに遅くなりました !それは26から20 gb/sに落としました、したがって、定数でない定数を置き換えることは 非最適化 につながります。真剣に、私はここで何が起こっているのかわかりません!しかし、新しいバージョンでclang++へ。 符号なし41959360000 0.677009秒 15.4884 gb /秒 uint64_t 41959360000 0.676909秒 15.4906 gb /秒 ちょっと待って、どうしたんださて、両方のバージョンが slow の15gb /秒に落ちた。したがって、定数でない定数を置き換えると、 both の場合、clang!のコードが遅くなります。私はベンチマークをまとめるために ivy bridge cpuを持つ同僚に依頼しました。彼は同様の結果を得たので、それはハズウェルではないようです。 2つのコンパイラがここで奇妙な結果を生み出すので、それもコンパイラのバグではないようです。ここにはamdのcpuがないので、intelでしかテストできませんでした。もっと狂気してください!最初の例(atol(argv[1])を持つもの)を取り、変数の前にstaticを置きます。static uint64_t size=atol(argv[1])<<20; これがg ++での結果です。 符号なし41959360000 0.396728秒 26.4306 gb /秒 uint64_t 41959360000 0.509484秒 20.5811 gb /秒 yay、さらに別の選択肢。 u32の高速26gb/sはまだありますが、少なくとも13gb/sから20gb/sのバージョンまでu64を取得することができました。 私の同僚のpcでは、u64バージョンはu32バージョンよりもさらに速くなり、すべての中で最も速い結果が得られました。 悲しいことに、これはg++に対してのみ機能します、clang++はstaticを気にしないようです。私の質問これらの結果を説明できますか?特に: u32とu64の間にそのような違いがあるのはどうしてですか? どのようにして、定数でないバッファサイズを定数のバッファサイズに置き換えると、最適ではないコードを発生させることができますか? staticキーワードを挿入すると、どのようにしてu64ループが速くなるのでしょうか。私の同僚のコンピュータの元のコードよりさらに速いです! 最適化は扱いにくい領域であることはわかっていますが、このような小さな変更で実行時間が 100%の差 になることや、一定のバッファサイズのような小さな要素で結果が完全に混同されることはありません。もちろん、私はいつも26 gb /秒でポップカウントできるバージョンを持っていたいです。私が考えることができる唯一の信頼できる方法はこの場合のためにアセンブリをコピーペーストしてインラインアセンブリを使うことです。これは私が小さな変更で気が狂うように見えるコンパイラを取り除くことができる唯一の方法です。どう思いますか?ほとんどのパフォーマンスで確実にコードを取得する方法はありますか?分解これがさまざまな結果の逆アセンブリです。26 gb /秒のバージョンから g ++/u32/non-const bufsize :0x400af8: lea 0x1(%rdx),%eax popcnt (%rbx,%rax,8),%r9 lea 0x2(%rdx),%edi popcnt (%rbx,%rcx,8),%rax lea 0x3(%rdx),%esi add %r9,%rax popcnt (%rbx,%rdi,8),%rcx add $0x4,%edx add %rcx,%rax popcnt (%rbx,%rsi,8),%rcx add %rcx,%rax mov %edx,%ecx add %rax,%r14 cmp %rbp,%rcx jb 0x400af8 g ++/u64/non-const bufsizeからの13 gb/sバージョン :0x400c00: popcnt 0x8(%rbx,%rdx,8),%rcx popcnt (%rbx,%rdx,8),%rax add %rcx,%rax popcnt 0x10(%rbx,%rdx,8),%rcx add %rcx,%rax popcnt 0x18(%rbx,%rdx,8),%rcx add $0x4,%rdx add %rcx,%rax add %rax,%r12 cmp %rbp,%rdx jb 0x400c00 clang ++/u64/non-const bufsizeからの15 gb/sバージョン :0x400e50: popcnt (%r15,%rcx,8),%rdx add %rbx,%rdx popcnt 0x8(%r15,%rcx,8),%rsi add %rdx,%rsi popcnt 0x10(%r15,%rcx,8),%rdx add %rsi,%rdx popcnt 0x18(%r15,%rcx,8),%rbx add %rdx,%rbx add $0x4,%rcx cmp %rbp,%rcx jb 0x400e50 g ++/u32およびu64/const bufsizeから20 gb/sのバージョン :0x400a68: popcnt (%rbx,%rdx,1),%rax popcnt 0x8(%rbx,%rdx,1),%rcx add %rax,%rcx popcnt 0x10(%rbx,%rdx,1),%rax add %rax,%rcx popcnt 0x18(%rbx,%rdx,1),%rsi add $0x20,%rdx add %rsi,%rcx add %rcx,%rbp cmp $0x100000,%rdx jne 0x400a68 clang ++/u32&u64/const bufsizeからの15 gb /秒バージョン :0x400dd0: popcnt (%r14,%rcx,8),%rdx add %rbx,%rdx popcnt 0x8(%r14,%rcx,8),%rsi add %rdx,%rsi popcnt 0x10(%r14,%rcx,8),%rdx add %rsi,%rdx popcnt 0x18(%r14,%rcx,8),%rbx add %rdx,%rbx add $0x4,%rcx cmp $0x20000,%rcx jb 0x400dd0 興味深いことに、最速(26 gb/s)のバージョンも最長です。 leaを使用する唯一の解決策のようです。ジャンプするのにjbを使うバージョンもあれば、jneを使うバージョンもあります。しかし、それとは別に、すべてのバージョンは同等のようです。 100%のパフォーマンス格差がどこから生じるのか私にはわかりませんが、私は議会を解読することに熟練しすぎていません。最も遅い(13 gb /秒)バージョンでさえ、とても短くて良いように見えます。誰もがこれを説明できますか?学んだ教訓この質問に対する答えがどうであろうと関係ありません。本当にホットなループでは、every detailが重要になる可能性があることを学びました、hot codeに関連がないような詳細でさえ。ループ変数にどのような型を使用するかについては一度も考えたことがありませんが、このような小さな変更によって100%の違いが生じる可能性があります。 size変数の前にstaticキーワードを挿入したときに見たように、バッファの格納タイプでさえも、大きな違いがあります。将来的には、システムパフォーマンスにとって非常に重要な、厳密でホットなループを作成するときに、さまざまなコンパイラでさまざまな代替手段を常にテストする予定です。興味深いことに、私はすでに4回ループを展開したにもかかわらず、パフォーマンスの違いがまだ非常に大きいことです。ですから、あなたがアンロールしても、あなたはまだ大きなパフォーマンスの逸脱に見舞われる可能性があります。とてもおもしろい。
回答: 私はこれを visual studio 2013 express で試しましたが、インデックスの代わりにポインタを使っていました。これは、アドレッシングがoffset + register +(register << 3)ではなく、offset + registerであるためと思われます。 c++コード. uint64_t* bfrend = buffer+(size/8); uint64_t* bfrptr; // ... { startp = chrono::system_clock::now(); count = 0; for (unsigned k = 0; k < 10000; k++){ // tight unrolled loop with uint64_t for (bfrptr = buffer; bfrptr < bfrend;){ count += __popcnt64(*bfrptr++); count += __popcnt64(*bfrptr++); count += __popcnt64(*bfrptr++); count += __popcnt64(*bfrptr++); } } endp = chrono::system_clock::now(); duration = chrono::duration_cast<std::chrono::nanoseconds>(endp-startp).count(); cout << "uint64_t\t" << count << '\t' << (duration/1.0e9) << " sec \t" << (10000.0*size)/(duration) << " gb/s" << endl; } アセンブリコード:r10 = bfrptr、r15 = bfrend、rsi = count、rdi =バッファ、r13 = k。$ll5@main: mov r10, rdi cmp rdi, r15 jae short $ln4@main npad 4 $ll2@main: mov rax, qword ptr [r10+24] mov rcx, qword ptr [r10+16] mov r8, qword ptr [r10+8] mov r9, qword ptr [r10] popcnt rdx, rax popcnt rax, rcx add rdx, rax popcnt rax, r8 add r10, 32 add rdx, rax popcnt rax, r9 add rsi, rax add rsi, rdx cmp r10, r15 jb short $ll2@main $ln4@main: dec r13 jne short $ll5@main