4

いくつかのアルゴリズムのパフォーマンスを測定できるように、インストラクターから次のコードが提供されました。

#include <stdio.h>
#include <unistd.h>

static unsigned cyc_hi = 0, cyc_lo = 0;

static void access_counter(unsigned *hi, unsigned *lo) {
    asm("rdtsc; movl %%edx,%0; movl %%eax,%1"
    : "=r" (*hi), "=r" (*lo)
    : /* No input */
    : "%edx", "%eax");
}

void start_counter() {
    access_counter(&cyc_hi, &cyc_lo);
}

double get_counter() {
    unsigned ncyc_hi, ncyc_lo, hi, lo, borrow;
    double result;

    access_counter(&ncyc_hi, &ncyc_lo);

    lo = ncyc_lo - cyc_lo;
    borrow = lo > ncyc_lo;
    hi = ncyc_hi - cyc_hi - borrow;

    result = (double) hi * (1 << 30) * 4 + lo;

    return result;
}

ただし、このコードを異なる CPU 周波数のマシンに移植できるようにする必要があります。そのために、コードが実行されているマシンの CPU 周波数を次のように計算しようとしています。

int main(void)
{
    double c1, c2;

    start_counter();

    c1 = get_counter();
    sleep(1);
    c2 = get_counter();

    printf("CPU Frequency: %.1f MHz\n", (c2-c1)/1E6);
    printf("CPU Frequency: %.1f GHz\n", (c2-c1)/1E9);

    return 0;
}

問題は、結果が常に 0 であり、その理由が理解できないことです。Linux (Arch) を VMware のゲストとして実行しています。

友人のマシン (MacBook) では、ある程度は機能しています。つまり、結果は 0 より大きくなりますが、CPU 周波数が固定されていないため可変です (修正しようとしましたが、何らかの理由で修正できませんでした)。彼は Linux (Ubuntu) をホストとして実行している別のマシンを持っていて、それも 0 を報告しています。

なぜこれが起こっているのか、どうすれば修正できますか?

4

5 に答える 5

2

さて、他の回答は役に立たなかったので、もっと詳しく説明しようと思います. 問題は、最新の CPU が順不同で命令を実行できることです。コードは次のように始まります。

rdtsc
push 1
call sleep
rdtsc

ただし、最新の CPU は必ずしも元の順序で命令を実行するとは限りません。元の順序にもかかわらず、CPU は (ほとんど) 以下のように自由に実行できます。

rdtsc
rdtsc
push 1
call sleep

この場合、2 つの s の差rdtscが (少なくとも非常に近い) 0 になる理由は明らかです。これを防ぐには、CPU が順不同で実行するように再配置しない命令を実行する必要があります。そのために使用する最も一般的な命令はCPUID. CPUID私がリンクした他の回答は、このタスクを正しく/効果的に使用するために必要な手順について、(メモリが機能する場合)そこから大まかに開始する必要があります。

もちろん、Tim Post が正しかった可能性ありますし、仮想マシンが原因で問題が発生している可能性もあります。それでも、現状では、コードが実際のハードウェアでも正しく動作するという保証はありません。

編集:コード機能する理由について:まず第一に、命令が順不同で実行される可能性があるという事実は、それらそうであることを保証するものではありません。第二に、(少なくともいくつかの実装には)再配置sleepを防ぐシリアル化命令が含まれてrdtscいる可能性がありますが、他の実装には含まれていない (または含まれている可能性がありますが、特定の (ただし不特定の) 状況でのみ実行される) 可能性があります)。

残っているのは、ほとんどすべての再コンパイルで、またはある実行と次の実行の間でさえも変化する可能性のある動作です。非常に正確な結果を何十回も続けて生成し、(ほとんど) まったく説明のつかない理由 (たとえば、まったく別のプロセスで発生した何か) で失敗する可能性があります。

于 2010-05-11T21:47:05.277 に答える
2

あなたのコードの何が問題なのかははっきりとは言えませんが、このような単純な命令に対してかなりの不要な作業を行っています。rdtscコードを大幅に簡素化することをお勧めします。64 ビット演算を実行する必要はありません。また、その演算の結果を double として格納する必要もありません。インライン asm で個別の出力を使用する必要はありません。GCC に eax と edx を使用するように指示できます。

このコードを大幅に簡略化したバージョンを次に示します。

#include <stdint.h>

uint64_t rdtsc() {
    uint64_t ret;

# if __WORDSIZE == 64
    asm ("rdtsc; shl $32, %%rdx; or %%rdx, %%rax;"
        : "=A"(ret)
        : /* no input */
        : "%edx"
    );
#else
    asm ("rdtsc" 
        : "=A"(ret)
    );
#endif
    return ret;
}

また、これから取得している値を出力して、0 を取得しているかどうかを確認することを検討する必要があります。

于 2010-05-11T22:15:32.027 に答える
1

VMWareについては、このスレッドだけでなく、時間管理仕様(PDFリンク)もご覧ください。TSCの手順は次のとおりです(ゲストOSによって異なります)。

  • 実際のハードウェアに直接渡されます(PVゲスト)
  • VMがホストプロセッサ(Windowsなど)で実行されているのサイクルをカウントする

#2では、VMがホストプロセッサで実行されている間は注意してください。私が正しく思い出せば、同じ現象がXenにも当てはまります。本質的に、コードは準仮想化ゲストで期待どおりに機能するはずです。エミュレートされた場合、一貫性のようなハードウェアを期待することはまったく不合理です。

于 2010-05-11T22:01:49.983 に答える
0

うーん、私は肯定的ではありませんが、問題は次の行の中にあるのではないかと思います:

結果 = (double) hi * (1 << 30) * 4 + lo;

「符号なし」でそのような巨大な乗算を安全に実行できるかどうかは疑わしいです...それは多くの場合32ビットの数値ではありませんか? ...安全に 2^32 を掛けることができず、最後に 2^30 に追加された "* 4" として追加する必要があったという事実だけで、この可能性がすでに示唆されています...必要があるかもしれません各サブコンポーネント hi と lo を double に変換し (最後の 1 つではなく)、2 つの double を使用して乗算を行います。

于 2011-11-09T14:17:24.540 に答える