特定の関数をベンチマークすることにしたので、単純に次のようなコードを書きます。
#include <ctime>
#include <iostream>
int SlowCalculation(int input) { ... }
int main() {
std::cout << "Benchmark running..." << std::endl;
std::clock_t start = std::clock();
int answer = SlowCalculation(42);
std::clock_t stop = std::clock();
double delta = (stop - start) * 1.0 / CLOCKS_PER_SEC;
std::cout << "Benchmark took " << delta << " seconds, and the answer was "
<< answer << '.' << std::endl;
return 0;
}
同僚は、コードの並べ替えを避けるために変数start
とstop
変数を宣言する必要があると指摘しました。volatile
彼は、オプティマイザーが、たとえば、次のようにコードを効果的に並べ替えることができると提案しました。
std::clock_t start = std::clock();
std::clock_t stop = std::clock();
int answer = SlowCalculation(42);
最初は、このような極端な並べ替えが許可されていることに懐疑的でしたが、いくつかの調査と実験の後、許可されていることがわかりました。
しかし、揮発性は適切な解決策とは思えませんでした。本当にメモリマップドI / Oのためだけに揮発性ではありませんか?
それにもかかわらず、私volatile
は、ベンチマークにかなり長い時間がかかっただけでなく、実行ごとに非常に一貫性がないことも発見しました. volatile を使用しない場合 (そして幸運にもコードが並べ替えられていないことを確認した場合)、ベンチマークは一貫して 600 ~ 700 ミリ秒かかりました。揮発性では、1200 ミリ秒かかることが多く、5000 ミリ秒以上かかることもありました。2 つのバージョンの逆アセンブル リストには、レジスタの選択が異なること以外、実質的に違いはありませんでした。これは、そのような圧倒的な副作用を持たないコードの並べ替えを回避する別の方法があるかどうか疑問に思います.
私の質問は:
このようなベンチマーク コードでコードの並べ替えを防ぐ最善の方法は何ですか?
私の質問は、これ(並べ替えではなく省略を回避するために volatile を使用することに関するものでした)、これ(並べ替えを防ぐ方法に答えなかった)、およびこれ(問題がコードの並べ替えまたはデッドコードであるかどうかを議論したもの)に似ています排除)。3 つすべてがこの正確なトピックを扱っていますが、実際に私の質問に答えるものはありません。
更新:答えは、私の同僚が間違っていたようで、このような並べ替えは標準と一致していません。私はそう言ったすべての人に賛成票を投じ、賞金をマキシムに授与します.
私が説明したように、Visual Studio 2010 がクロック呼び出しを並べ替えた (この質問のコードに基づく) 1 つのケースを見てきました(64 ビット ビルドのみ)。Microsoft Connect でバグを報告できるように、それを説明する最小限のケースを作成しようとしています。
volatile はメモリへの読み取りと書き込みを強制するため、はるかに遅くする必要があると述べた人にとって、これは発行されるコードと完全に一致していません。この質問に対する私の回答では、volatile を使用する場合と使用しない場合のコードの逆アセンブリを示します。ループ内では、すべてがレジスタに保持されます。唯一の大きな違いは、レジスタの選択のようです。x86 アセンブリを十分に理解していないため、不揮発性バージョンのパフォーマンスが一貫して高速である一方で、揮発性バージョンのパフォーマンスが一貫して(場合によっては劇的に) 遅い理由を知ることができません。