4

私は次の機能を持っています

double single_channel_add(int patch_top_left_row, int patch_top_left_col, 
        int image_hash_key, 
        Mat* preloaded_images,
        int* random_values){

    int first_pixel_row = patch_top_left_row + random_values[0];
    int first_pixel_col = patch_top_left_col + random_values[1];
    int second_pixel_row = patch_top_left_row + random_values[2];
    int second_pixel_col = patch_top_left_col + random_values[3];

    int channel = random_values[4];

    Vec3b* first_pixel_bgr = preloaded_images[image_hash_key].ptr<Vec3b>(first_pixel_row, first_pixel_col);
    Vec3b* second_pixel_bgr = preloaded_images[image_hash_key].ptr<Vec3b>(second_pixel_row, second_pixel_col);

    return (*first_pixel_bgr)[channel] + (*second_pixel_bgr)[channel];
}

patch_top_left_rowこれは、との値が異なる場合、約150万回呼び出されますpatch_top_left_col。これは実行に約2秒かかります。first_pixel_rowなどの計算を引数ではなくハードコードされた数値に変更すると(以下に表示)、1秒未満で実行され、理由がわかりません。コンパイラはここで何か賢いことをしていますか(私はgccクロスコンパイラを使用しています)?

double single_channel_add(int patch_top_left_row, int patch_top_left_col, 
        int image_hash_key, 
        Mat* preloaded_images,
        int* random_values){

        int first_pixel_row = 5 + random_values[0];
        int first_pixel_col = 6 + random_values[1];
        int second_pixel_row = 8 + random_values[2];
        int second_pixel_col = 10 + random_values[3];
            int channel = random_values[4];

    Vec3b* first_pixel_bgr = preloaded_images[image_hash_key].ptr<Vec3b>(first_pixel_row, first_pixel_col);
    Vec3b* second_pixel_bgr = preloaded_images[image_hash_key].ptr<Vec3b>(second_pixel_row, second_pixel_col);

    return (*first_pixel_bgr)[channel] + (*second_pixel_bgr)[channel];
}

編集:

引数を使用して関数の2つのバージョンからアセンブリを貼り付けました:定数 を使用してhttp://pastebin.com/tpCi8c0F:http://pastebin.com/bV0d7QH7

編集:

-O3でコンパイルした後、次のクロックティックと速度が得られます。

引数の使用:1990000ティックと1.99秒定数の使用:330000ティックと0.33秒

編集:-03コンパイルでargumenstを使用:http://pastebin.com/fW2HCnHc -03コンパイル 定数を使用:http: //pastebin.com/FHs68Agi

4

2 に答える 2

5

x86プラットフォームには、レジスタに小さな整数を非常にすばやく追加する命令があります。これらの命令はlea(別名「実効アドレスのロード」)命令であり、構造体などのアドレスオフセットを計算するためのものです。追加される小整数は、実際には命令の一部です。スマートコンパイラは、これらの命令が非常に高速であることを認識しており、アドレスが含まれていない場合でも加算に使用します。

定数を少なくとも24ビット長のランダムな値に変更すると、スピードアップの多くが消えるのがわかるでしょう。

次に、これらの定数は既知の値です。コンパイラーは、これらの値が可能な限り最も効率的な方法でレジスターに収まるように調整するために多くのことを行うことができます。引数がある場合、引数がレジスターで渡されない限り(そして、関数の引数が多すぎてその呼び出し規約を使用できないと思います)、コンパイラーはスタックオフセットロード命令を使用してメモリから数値をフェッチするしかありません。これは特に遅い命令などではありませんが、定数を使用すると、コンパイラは、命令自体から数値をフェッチするよりもはるかに高速に何かを実行できます。指示は、これleaの最も極端な例にすぎません。

編集:アセンブリを貼り付けたので、物事ははるかに明確になります

非定数コードでは、次のように追加が行われます。

addl    -68(%rbp), %eax

これにより、スタックからオフセットの値がフェッチ-68(%rpb)され、レジスタに追加%eax%されます。

定数コードでは、次のように追加が行われます。

addl    $5, %eax

実際の数値を見ると、次のことがわかります。

0138 83C005

追加される定数が小さな値として命令に直接エンコードされていることは明らかです。これは、いくつかの理由から、スタックオフセットから値をフェッチするよりもはるかに高速にフェッチできます。まず、それは小さいです。第二に、それは分岐のない命令ストリームの一部です。そのため、プリフェッチされてパイプライン化され、いかなる種類のキャッシュストールも発生しません。

そのため、指示についての私の推測leaは正しくありませんでしたが、私はまだ正しい方向に進んでいました。定数バージョンは、レジスタに小さな整数を追加することを特に目的とした小さな命令を使用します。非定数バージョンは、スタックオフセットからサイズが不確定である可能性のある整数をフェッチする必要があります(したがって、下位ビットだけでなくすべてのビットをフェッチする必要があります)。オフセットおよびスタックベースアドレス)。

-O3編集2:結果を投稿したので

さて、それは今でははるかに混乱しています。問題の関数は明らかにインライン化されており、インライン化された関数のコードと呼び出し元の関数のコードの間を1トンほどジャンプします。適切な分析を行うには、ファイル全体の元のコードを確認する必要があります。

しかし、私が今起こっていることを強く疑っているのは、から取得された値の予測不可能性がget_random_number_in_range、コンパイラーが使用できる最適化オプションを大幅に制限していることです。get_random_number_in_range実際、定数バージョンでは、値が破棄されて使用されないため、呼び出す必要さえないように見えます。


との値はどこかのループで生成されるpatch_top_left_rowと想定しています。patch_top_left_colこのループをこの関数にプッシュします。値がループの一部として生成されることをコンパイラーが認識している場合、非常に多くの最適化オプションが利用可能です。極端な場合、さまざまなSSEまたは3dnowの一部であるSIMD命令の一部を使用できます。定数を使用するバージョンよりも1トン速くするための命令スイート。

もう1つのオプションは、この関数をインラインにすることです。これにより、コンパイラーは、呼び出されたループに関数を挿入しようとする必要があることを示唆します。コンパイラーがヒントを受け取る場合(この関数は少し大きいので、コンパイラーはそうではないかもしれません)、ループを関数に詰め込んだ場合とほとんど同じ効果があります。

于 2012-11-02T18:40:51.063 に答える
2

フォーマットの2進算術演算は、immediate constant vs. memoryフォーマットの2進算術演算よりも高速なコードを生成することが期待されますがmemory vs. memory、特にその関数内に他の演算があることを考えると、観察されるタイミング効果は極端すぎるように見えます。

コンパイラが関数をインライン化することを決定したのでしょうか?インライン化により、コンパイラーは、呼び出し元のコードでこれらのパラメーターを準備/計算するステップを含め、2番目のバージョンの未使用patch_top_left_rowのパラメーターに関連するすべてのものを簡単に削除できます。patch_top_left_col

技術的には、これは関数がインライン化されていなくても実行できますが、一般的にはより複雑です。

于 2012-11-02T18:23:38.383 に答える