2016-04-16 11 views
0

私は、この特定の行で混乱がある - 次のコードの>サイクルカウンターでプログラムの実行時間を計測

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

:私は理解できない事が理由ハイテクされていることである

void access_counter(unsigned *hi, unsigned *lo) 
// Set *hi and *lo to the high and low order bits of the cycle 
// counter. 
{ 
    asm("rdtscp; movl %%edx,%0; movl %%eax,%1" // Read cycle counter 
     : "=r" (*hi), "=r" (*lo)    // and move results to 
     : /* No input */      // the two outputs 
     : "%edx", "%eax"); 
} 

double get_counter() 
// Return the number of cycles since the last call to start_counter. 
{ 
    unsigned ncyc_hi, ncyc_lo; 
    unsigned hi, lo, borrow; 
    double result; 

    /* Get cycle counter */ 
    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; 
    if (result < 0) { 
    fprintf(stderr, "Error: counter returns neg value: %.0f\n", result); 
    } 
    return result; 
} 

を2^30で乗算してから4?それに低かった?誰かがこのコード行で何が起こっているのかを説明してください。私は、ハイとローが含むものを知っています。

+0

rdtscpのドキュメントを見ましたか? 64ビットの数値を返します。 eaxの下位32ビットとedxの上位32ビット。分かりやすい実装では、access_counterは64ビットの整数を返します。なぜこれが私が想像することのできない浮動小数点に変わっているのですか? –

+0

私がそれをしている間、asmは間違って書かれています。 1)コンパイラに出力やクローバーを通知せずにecxを変更する(非常に悪い)。 2)不要なmovステートメントが2つあります(時間と貴重なレジスタの両方を浪費します)。どうすれば 'unsigned int a; unsigned long long b = __builtin_ia32_rdtscp(&a); '?(明らかに定義されていない?)cyc_lo&cyc_hiに64ビットの番号を使用した場合、newtime-oldtimeを簡単に減算します。 –

答えて

1

短い答え:

すなわちラインは、浮動小数点数に2つの32ビット値として格納される64ビット整数をオン。

なぜコードは64ビット整数を使用しないのですか?まあ、gccは長い間64ビットの数字をサポートしていましたが、おそらくこのコードはそれより前です。その場合、大きい数字をサポートする唯一の方法は、それらを浮動小数点数に入れることです。

長い答え:

まず、あなたはどのようにrdtscp作品を理解する必要があります。このアセンブラ命令が呼び出されると、2つのことが行われます。

1)ecxをIA32_TSC_AUX MSRに設定します。私の経験上、これは一般的にecxがゼロに設定されることを意味します。 2)edx:eaxにプロセッサのタイムスタンプカウンタの現在の値を設定します。これは、カウンタの下位64ビットがeaxに入り、上位32ビットがedxにあることを意味します。

これを念頭に置いて、コードを見てみましょう。 get_counterから呼び出されると、access_counterはedxを 'ncyc_hi'に入れ、eaxを 'ncyc_lo'に入れます。その後、get_counterは次のようになります。

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

これはどうしますか?

時刻は2つの異なる32ビット番号で保存されているので、どれだけの時間が経過したかを知りたい場合は、古い時刻と新しい時刻の違いを見つけるために少し作業を行う必要があります。それが完了すると、結果はhi/loに(2つの32ビットの数値を使用して)格納されます。

これは、あなたの質問に私たちをもたらします。

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

我々は、単一の64ビット値に2つの32ビットの値を変換すると、次のようになり、64ビット整数を使用することができた場合:多分ようにそれを見て、あなたはビットシフトに使用されていない場合は

unsigned long long result = hi; // put hi into the 64bit number. 
result <<= 32;     // shift the 32 bits to the upper part of the number 
results |= low;     // add in the lower 32bits. 

これは役に立ちます。 LO = 1と高= 2、その後、16進数として表現した場合:

result = hi; 0x0000000000000002 
result <<= 32; 0x0000000200000000 
result |= low; 0x0000000200000001 

しかし、我々は、コンパイラが64ビットの整数をサポートしていないと仮定した場合、それは動作しません。浮動小数点数は大きな値を保持できますが、シフトをサポートしていません。したがって、左シフトを使用してを使わずに、「ハイ」を32ビット左にシフトする方法を見つけ出す必要があります。

ここで、1だけ左にシフトすることは、実際には2を乗算することと同じです。左に2をシフトするのは、4を掛けることと同じです。[省略...]で左にシフトする32で左にシフトすることは、4,294,967,296を掛けることと同じです。

驚くべき偶然

、4,294,967,296 ==(1 < < 30)* 4

なぜその複雑な方法でそれを書くのか?まあ、4,294,967,296はかなり大きい数字です。実際、32ビットの整数に収まるには大きすぎます。つまり、64ビット整数をサポートしていないコンパイラでは、ソースコードに記述すると、処理する方法がわからなくなる可能性があります。このように書かれていると、コンパイラは浮動小数点命令を生成することができます。

現在のコードが間違っている理由:

このコードのバリエーションは、長い時間のために、インターネットを中心に放浪してきたように見えます。もともと(私が仮定すると)access_counterはrdtscpの代わりにrdtscを使って書かれていました。私はrdtscがecxを設定していないことを指摘する以外に、2つの違い(googleの違い)を説明しようとはしません。rdtscpはそうします。 rdtscをrdtscpに変更した人はそれを知らず、インラインアセンブラの内容を反映するために調整しませんでした。これにもかかわらずあなたのコードはうまくいくかもしれませんが、代わりに何か変なことが起こるかもしれません。これを修正するには、次のようにします。

asm("rdtscp; movl %%edx,%0; movl %%eax,%1" // Read cycle counter 
    : "=r" (*hi), "=r" (*lo)     // and move results to 
    : /* No input */       // the two outputs 
    : "%edx", "%eax", "%ecx"); 

これは機能しますが、最適ではありません。レジスタは、i386上の貴重で希少なリソースです。この小さな断片は5つを使用しています。わずかな変更を加えて:

asm("rdtscp" // Read cycle counter 
    : "=d" (*hi), "=a" (*lo) 
    : /* No input */ 
    : "%ecx"); 

ここではアセンブリステートメントが2つ少なくなり、3つのレジスタしか使用しません。

でも、これが最善の方法ではありません。 「

unsigned int a; 
unsigned long long result; 

result = __builtin_ia32_rdtscp(&a); 

:このコードが書かれていたので、(おそらく長い)時間で、gccはTSCを読み取るために64ビット整数のサポートと機能の両方を追加しましたので、あなたはすべてのASMを使用する必要はありません'はecxで返された(無駄な)値です。関数呼び出しにはそれが必要ですが、返された値は無視できます。 (私はあなたの既存のコードを前提としない)ので、

、代わりにこのような何かをやって:

unsigned cyc_hi, cyc_lo; 

access_counter(&cyc_hi, &cyc_lo); 
// do something 
double elapsed_time = get_counter(); // Find the difference between cyc_hi, cyc_lo and the current time 

我々が行うことができます:

unsigned int a; 
unsigned long long before, after; 

before = __builtin_ia32_rdtscp(&a); 
// do something 
after = __builtin_ia32_rdtscp(&a); 
unsigned long long elapsed_time = after - before; 

これは短い、ハード使用していません最良のコードを読みやすく、維持し、生成するのが簡単です。

しかし、比較的最近のバージョンのgccが必要です。

関連する問題