web-dev-qa-db-ja.com

__asm__ __volatile__はCで何をしますか?

私はいくつかのCコードを調べました

http://www.mcs.anl.gov/~kazutomo/rdtsc.html

inline」、「asm」などのようなものを使用します以下:

code1:

static __inline__ tick gettick (void) {
    unsigned a, d;
    __asm__ __volatile__("rdtsc": "=a" (a), "=d" (d) );
    return (((tick)a) | (((tick)d) << 32));
}

code2:

volatile int  __attribute__((noinline)) foo2 (int a0, int a1) {
    __asm__ __volatile__ ("");
}

Code1とcode2は何をしているのだろうと思っていましたか?

37
user3692521

__volatile__ブロックの__asm__修飾子は、コンパイラのオプティマイザーにコードをそのまま実行させます。これがないと、オプティマイザーは、完全に削除するか、ループから持ち上げてキャッシュすることができると考えます。

これは、次のようなrdtsc命令に役立ちます。

__asm__ __volatile__("rdtsc": "=a" (a), "=d" (d) )

これには依存関係がないため、コンパイラは値をキャッシュできると想定する場合があります。揮発性は、新しいタイムスタンプを強制的に読み取るために使用されます。

このように単独で使用する場合:

__asm__ __volatile__ ("")

実際には何も実行しません。ただし、これを拡張して、メモリアクセス命令の並べ替えを許可しないコンパイル時のメモリバリアを取得できます。

__asm__ __volatile__ ("":::"memory")

rdtsc命令はvolatileの良い例です。 rdtscは通常、一部の命令の実行にかかる時間を計る必要がある場合に使用されます。 r1r2の実行時間を計る次のようなコードを想像してください。

__asm__ ("rdtsc": "=a" (a0), "=d" (d0) )
r1 = x1 + y1;
__asm__ ("rdtsc": "=a" (a1), "=d" (d1) )
r2 = x2 + y2;
__asm__ ("rdtsc": "=a" (a2), "=d" (d2) )

ここで、コンパイラは実際にタイムスタンプをキャッシュすることが許可されており、有効な出力は、各行の実行に正確に0クロックかかったことを示す場合があります。明らかにこれはあなたが望むものではないので、キャッシュを防ぐために__volatile__を導入します:

__asm__ __volatile__("rdtsc": "=a" (a0), "=d" (d0))
r1 = x1 + y1;
__asm__ __volatile__("rdtsc": "=a" (a1), "=d" (d1))
r2 = x2 + y2;
__asm__ __volatile__("rdtsc": "=a" (a2), "=d" (d2))

これで毎回新しいタイムスタンプを取得できますが、コンパイラーとCPUの両方がこれらすべてのステートメントを並べ替えることができるという問題がまだあります。 r1とr2がすでに計算された後、asmブロックを実行することになります。これを回避するには、シリアル化を強制するいくつかの障壁を追加します。

__asm__ __volatile__("mfence;rdtsc": "=a" (a0), "=d" (d0) :: "memory")
r1 = x1 + y1;
__asm__ __volatile__("mfence;rdtsc": "=a" (a1), "=d" (d1) :: "memory")
r2 = x2 + y2;
__asm__ __volatile__("mfence;rdtsc": "=a" (a2), "=d" (d2) :: "memory")

ここでmfence命令に注意してください。これはCPU側のバリアを強制し、揮発性ブロック内の「メモリ」指定子はコンパイル時のバリアを強制します。最新のCPUでは、mfence:rdtscrdtscpに置き換えると、より効率的なものになります。

57
Cory Nelson

asmは、ネイティブアセンブリコードをCソースコードに含めるためのものです。例えば。

_int a = 2;
asm("mov a, 3");
printf("%i", a); // will print 3
_

コンパイラにはさまざまなバリエーションがあります。 ___asm___は同義語である必要があり、おそらくコンパイラ固有の違いがいくつかあります。

volatileは、変数を外部から変更できることを意味します(別名、Cプログラムによるものではありません)。たとえば、メモリアドレス_0x0000x1234_がデバイス固有のインターフェイスにマッピングされるマイクロコントローラーをプログラミングする場合(つまり、GameBoyのコーディングの場合、ボタン/画面などにこの方法でアクセスします)

_volatile std::uint8_t* const button1 = 0x00001111;
_

これにより、コードによって変更されない限り、_*button1_に依存しないコンパイラーの最適化が無効になりました。

また、変数が別のスレッドによって変更される可能性のあるマルチスレッドプログラミング(今日ではもう必要ない?)でも使用されます。

inlineは、コンパイラーが関数の呼び出しを「インライン化」するためのヒントです。

_inline int f(int a) {
    return a + 1
}

int a;
int b = f(a);
_

これは、fへの関数呼び出しではなく、_int b = a + 1_にコンパイルする必要があります。まるでfがマクロのように。コンパイラは、ほとんどの場合、関数の使用法/内容に応じてこの最適化を自動的に実行します。この例の___inline___には、より具体的な意味があるかもしれません。

同様に、__attribute__((noinline))(GCC固有の構文)は、関数がインライン化されないようにします。

5
tmlen