4

私は、最適化された Apple LLVM 4.0 でコンパイルする ios プロジェクトに取り組んでいます。関数の 2 つの異なるバージョンを実装しました。1 つは C で、もう 1 つは NEON です。私は彼らのパフォーマンスを互いにテストしたかった. 私の考えは、両方を同じ回数呼び出してから、Time Profiler で調べて、それぞれに費やされた相対的な時間を確認することでした。もともと私のコードは次のように見えました

used_value = score_squareNEON(patch, image, current_pos);
used_value = score_squareC(patch, image, current_pos);

時間をプロファイリングすると、NEON コードはまったく表示されませんでした。次に試したのは

for(int i = 0; i < successively_bigger_numbers; i++)
{
    used_value = score_squareNEON(patch, image, current_pos);
{
used_value = score_squareC(patch, image, current_pos);

まだNEONコードからの貢献はありません。次は

used_value = score_squareNEON(patch, image, current_pos);
test = score_squareC(patch, image, current_pos);

テストが読み取られなかった場所。何もない。それで

test = score_squareNEON(patch, image, current_pos);
test = 0;
other_used_variable += test;
used_value = score_squareC(patch, image, current_pos);

まだ何もありません。最終的に両方の機能を実行させたのは

value = score_squareNEON(patch, image, current_pos);
test = score_squareC(patch, image, current_pos);
...
min = (value+test)/2; //before it was min=value;

また、非常に重要です。関数は両方とも、私がそれらを呼び出していた同じファイルで定義されていました。関数宣言を別のファイルに移動しようとすると、すべての例で両方が呼び出されます。

まず第一に、私はコンパイラに対して多くの敬意を払っています。第二に、関数が確実に呼び出されるようにするには、正確に何をする必要がありますか? これにより、これまで計時してきたすべてのことに疑問を抱くようになりました。通常のパターンだとどうなるか

timerStart();
functionCall();
timerEnd();

中間の関数は完全に最適化されますか? 毎回何らかの形でこれをチェックし始める必要がありますか、それとも使用できるトリックはありますか? コンパイラが関数呼び出し全体を最適化できるタイミングを管理するルールは何ですか?

4

2 に答える 2

5

また、非常に重要です。関数は両方とも、私がそれらを呼び出していた同じファイルで定義されていました。関数宣言を別のファイルに移動しようとすると、すべての例で両方が呼び出されます。

関数呼び出しに副作用がなく、その結果が使用されないことをコンパイラが証明できる場合、コンパイラは呼び出しを削除できます。それを証明できない場合、コンパイラが知る限り、関数には副作用がある可能性があり、それらを排除してはならないため、呼び出しを削除することはできません。

関数呼び出しの結果が代入される変数を宣言するだけで、コンパイラーはプログラム内で関数呼び出しを強制的に終了させることができます (6.7.3、N1570 の段落 7)。

volatile 修飾された型を持つオブジェクトは、実装に不明な方法で変更されたり、他の未知の副作用を持つ可能性があります。したがって、そのようなオブジェクトを参照する式は、5.1.2.3 で説明されているように、抽象マシンの規則に従って厳密に評価されます。さらに、すべてのシーケンス ポイントで、オブジェクトに最後に格納された値は、前述の未知の要因によって変更された場合を除き、抽象マシンによって規定された値と一致するものとします。volatile 修飾された型を持つオブジェクトへのアクセスを構成するものは、実装定義です。

C++の場合、私が知る限り、保証はもう少し明確ですが、1.9が優先されるべきだと思います:

プログラムの実行、1.9 (6) および (7):

抽象マシンの観測可能な動作は、揮発性データへの読み取りと書き込み、およびライブラリ I/O 関数の呼び出しのシーケンスです。

volatile 左辺値 (3.10) で指定されたオブジェクトへのアクセス、オブジェクトの変更、ライブラリ I/O 関数の呼び出し、またはこれらの操作のいずれかを行う関数の呼び出しはすべて、実行環境の状態の変化である副作用です。式の評価により、副作用が生じる場合があります。シーケンスポイントと呼ばれる実行シーケンスの特定のポイントでは、前の評価のすべての副作用が完了し、後続の評価の副作用は発生していません。

そして7.1.5.1では:

[注: volatile は、オブジェクトの値が実装によって検出できない方法で変更される可能性があるため、オブジェクトを含む積極的な最適化を回避するための実装へのヒントです。詳細なセマンティクスについては、1.9 を参照してください。一般に、volatile のセマンティクスは、C++ でも C と同じになるように意図されています。]

もちろん、それは では機能しませんvoid fun()

于 2012-08-02T18:00:53.130 に答える
4

コンパイラは、「観察可能な」結果が、言語によって定義された理想的な「仮想マシン」で記述したとおりにコードを実行することと区別できない限り、コードで必要なことを何でも実行できます。

「観察可能」には、実行時間、プロファイラーの結果、デバッガーを介して観察された変数などは含まれません。観察可能な動作は、揮発性オブジェクトへのアクセス、ファイルに書き込まれたデータ、入出力デバイスの処理と見なされます。

したがって、コードが実際に実行されることを確認するには、観察可能な正しい動作を生成するためにコードを実行する必要があることを確認する必要があります。通常、出力を保存して、印刷またはファイルに書き込むことができます(タイミングを計っているコードの外で)。別のオプションは、出力を揮発性変数に書き込むことです。

重要なもう 1 つのことは、コンパイラがコードを静的に評価できる場合、出力を出力しても、関数呼び出しはコンパイラの静的に計算された出力をロードするだけに削減される可能性があることです。これを回避するには、入力またはファイルまたは揮発性変数から読み取られたデータなど、静的に認識できない関数への入力を提供する必要がある場合があります。


もちろん、実際のプログラムではない方法で入力と出力を使用すると、タイミングに影響を与える可能性があります。したがって、パフォーマンスを測定する最も信頼できる方法は、テストしたい構成を正確に使用した実際のプログラムで測定することです。構成間で簡単に切り替えられるようにプログラムを作成し、両方をテストします。

于 2012-08-02T18:01:01.167 に答える