-1

次の奇妙な動作を目撃しました。ほとんど同じことを行う 2 つの関数があります。これらは、特定の操作を実行するのにかかるサイクル数を測定します。ある関数では、ループ内で変数をインクリメントします。他の場合は何も起こりません。変数は揮発性であるため、最適化されません。これらは関数です:

unsigned int _osm_iterations=5000;

double osm_operation_time(){
    // volatile is used so that j will not be optimized, and ++ operation
    // will be done in each loop
    volatile unsigned int j=0;
    volatile unsigned int i;
    tsc_counter_t start_t, end_t;
    start_t = tsc_readCycles_C();
    for (i=0; i<_osm_iterations; i++){
       ++j;
    }
    end_t = tsc_readCycles_C();
    if (tsc_C2CI(start_t) ==0 || tsc_C2CI(end_t) ==0 || tsc_C2CI(start_t) >= tsc_C2CI(end_t))
         return -1;
    return (tsc_C2CI(end_t)-tsc_C2CI(start_t))/_osm_iterations;
}

double osm_empty_time(){
    volatile unsigned int i;
    volatile unsigned int j=0;
    tsc_counter_t start_t, end_t;
    start_t = tsc_readCycles_C();
    for (i=0; i<_osm_iterations; i++){
        ;
    }
    end_t = tsc_readCycles_C();
    if (tsc_C2CI(start_t) ==0 || tsc_C2CI(end_t) ==0 || tsc_C2CI(start_t) >= tsc_C2CI(end_t))
        return -1;
    return (tsc_C2CI(end_t)-tsc_C2CI(start_t))/_osm_iterations;
}

そこには標準外の機能がいくつかありますが、管理できると確信しています。

問題は、最初の関数は4を返し、2 番目の関数 (おそらく少ない) は6を返しますが、2 番目の関数は最初の関数より明らかに少ないことです。

それは誰にとっても意味がありますか?

実際には、最初の関数を作成したので、2 番目の測定のループ オーバーヘッドを減らすことができました。それを行う方法はありますか(この方法では実際にはうまくいきません)?

私はUbuntuを使用しています(64ビットだと思います)。

どうもありがとう。

4

4 に答える 4

4

ここでいくつかのことがわかります。1 つは、2 つのループのコードが同一に見えることです。次に、コンパイラは、変数iと変数jが常に同じ値を持つことを認識し、そのうちの 1 つを最適化します。生成されたアセンブリを見て、実際に何が起こっているかを確認する必要があります。

もう 1 つの理論は、ループの内部本体への変更がコードのキャッシュ可能性に影響を与えたというものです。これにより、コードがキャッシュ ラインまたはその他のものを越えて移動した可能性があります。

コードは非常に単純であるため、正確なタイミング値を取得するのが難しい場合があります。5000 回の反復を行ったとしても、使用しているタイミング コードの誤差の範囲内であることがわかる場合があります。現代のコンピューターは、おそらく 1 ミリ秒よりもはるかに短い時間で実行できます。おそらく、反復回数を増やす必要がありますか?

生成されたアセンブリを gcc で表示するには、-S コンパイラ オプションを指定します

Q: GCC によって生成されたアセンブリ コードを確認するにはどうすればよいですか?

Q: C コードとそのアセンブリ翻訳を一緒に表示できるファイルを作成するにはどうすればよいですか?

A: -S (注: 大文字の S) スイッチを GCC に使用すると、アセンブリ コードが .s 拡張子の付いたファイルに出力されます。たとえば、次のコマンドです。

gcc -O2 -S -c foo.c

生成されたアセンブリ コードをファイル foo.s に残します。

C コードを変換先のアセンブリと一緒に表示する場合は、次のようなコマンド ラインを使用します。

gcc -c -g -Wa,-a,-ad [その他の GCC オプション] foo.c > foo.lst

これにより、結合された C/アセンブリ リストがファイル foo.lst に出力されます。

于 2009-03-23T00:11:47.973 に答える
0

コードの一部(この場合)の動作を本当にテストしようとしている"j++;"場合は、実際には次のことを行う方がよいでしょう。

1 /実行可能ファイル内の位置がコードに影響を与える可能性があるため、2つの別々の実行可能ファイルで実行します。

2 /経過時間ではなくCPU時間を使用していることを確認してください(何"tsc_readCycles_C()"が得られるかわかりません)。これは、他のタスクでロードされたCPUからの誤った結果を回避するためです。

"gcc -O0"3 /コンパイラの最適化(たとえば)をオフgccにして、結果を歪める可能性のある派手なものを入れないようにします。

volatile4 /配置など、実際の結果を使用するかどうかを心配する必要はありません。

printf ("%d\n",j);

ループの後、または:

FILE *fx = fopen ("/dev/null","w");
fprintf (fx, "%d\n", j);
fclose (fx);

出力がまったく必要ない場合。volatileがコンパイラーへの提案だったのか、強制されたのか思い出せません。

5 / 5,000回の反復は、「ノイズ」が測定値に影響を与える可能性があるローサイドでは少し見えます。たぶん、値が高いほど良いでしょう。"j++;"より大きなコードのタイミングを取り、プレースホルダーとして含めたばかりの場合、これは問題にならない可能性があります。

于 2009-03-23T01:27:03.347 に答える
0

特に反復回数が少ないため、この種のことを推測するのは難しい場合があります。ただし、発生する可能性のあることの1つは、増分が自由整数実行ユニットで実行され、iの値に依存しないため、ある程度の並列処理が得られる可能性があることです。

これは64ビットOSであるとおっしゃっていたので、x86_64アーキテクチャにはさらに多くのレジスタがあるため、これらすべての値がレジスタにあることはほぼ確実です。それ以外に、さらに多くの反復を実行して、結果がどれほど安定しているかを確認します。

于 2009-03-23T01:25:35.753 に答える
0

これに似たテストを実行しているとき、私は通常:

  1. 時間が少なくとも数秒、できれば(小さい)数十秒で測定されるようにしてください。
  2. プログラムを 1 回実行して、最初の関数を呼び出し、次に 2 番目、次に最初の関数、2 番目の関数、というように呼び出して、奇妙なキャッシュ ウォームアップの問題があるかどうかを確認します。
  3. プログラムを複数回実行して、実行全体でタイミングがどの程度安定しているかを確認します。

観察された結果を説明するのはまだ途方に暮れていますが、関数が適切に識別されていると確信している場合 (たとえば、以前にコピー アンド ペースト エラーがあったことを考えると、自明ではありません)、次に、アセンブラの出力を見ることが、残された主なオプションです。

于 2009-03-23T06:02:37.053 に答える