2012-02-27 12 views
11

は、次の凝縮のコードを考えてみましょう:__sync_add_and_fetchが32ビットシステム上の64ビット変数に対して動作するのはなぜですか?

/* Compile: gcc -pthread -m32 -ansi x.c */ 
#include <stdio.h> 
#include <inttypes.h> 
#include <pthread.h> 

static volatile uint64_t v = 0; 

void *func (void *x) { 
    __sync_add_and_fetch (&v, 1); 
    return x; 
} 

int main (void) { 
    pthread_t t; 
    pthread_create (&t, NULL, func, NULL); 
    pthread_join (t, NULL); 
    printf ("v = %"PRIu64"\n", v); 
    return 0; 
} 

変数は、マルチスレッドのプログラムカウンタであるので、私は、私はアトミックにインクリメントしたいuint64_t変数を持っています。 原子性を達成するために、私はGCCのatomic builtinsを使用します。

amd64システム(-m64)用にコンパイルすると、生成されたアセンブラコードがわかりやすくなります。 lock addqを使用すると、プロセッサはインクリメントをアトミックにすることを保証します。

400660:  f0 48 83 05 d7 09 20 lock addq $0x1,0x2009d7(%rip) 

しかし、同じCコードは、IA32システム(-m32)上の非常に複雑なASMコードを生成します。ここでは

804855a:  a1 28 a0 04 08   mov 0x804a028,%eax 
804855f:  8b 15 2c a0 04 08  mov 0x804a02c,%edx 
8048565:  89 c1     mov %eax,%ecx 
8048567:  89 d3     mov %edx,%ebx 
8048569:  83 c1 01    add $0x1,%ecx 
804856c:  83 d3 00    adc $0x0,%ebx 
804856f:  89 ce     mov %ecx,%esi 
8048571:  89 d9     mov %ebx,%ecx 
8048573:  89 f3     mov %esi,%ebx 
8048575:  f0 0f c7 0d 28 a0 04 lock cmpxchg8b 0x804a028 
804857c:  08 
804857d:  75 e6     jne 8048565 <func+0x15> 

は、私は理解していないものです:

  • lock cmpxchg8bは、変更された変数が期待値が依然としてターゲットアドレスにある場合にのみ書き込まれることを保証します。 compare-and-swapは、アトミックに行われることが保証されています。
  • しかし、 0x804855aと0x804855fの変数の読み取りがアトミックであることを保証するものは何ですか?

おそらく「ダーティリードが」あった場合、それは問題ではありませんが、誰かが問題がないことを短い証拠を概説してくださいだろうか?

さらに、生成されたコードが0x8048565ではなく0x80485565に戻るのはなぜですか?私はこれが正しかったのは、他の作家も変数をインクリメントするだけであるということです。これは、__sync_add_and_fetch機能のための関係する要件ですか?

答えて

16

読み出しがアトミックであることが保証されるため、それが正しく整列(と、それは1つのキャッシュラインに収まる)とIntelが仕様をこのように作られているため、Intelアーキテクチャマニュアル第1巻、4.4.1を参照しているに:

8バイト境界を横切る4バイト境界または クワッドワードオペランドを横切るワードまたはダブルワードオペランドは、アライメントされていないと考えられ、アクセスのために2つの別個のメモリバスサイクルが必要です。

3.18.1。1:

Pentiumプロセッサ(以降プロセッサ以降)は 以下の追加のメモリ操作が常にアトミック が行われることを保証する。

•読み取りまたは64ビットで整列クワッドワードを書き込む 境界

16ビット、32ビット・データ・バス内 に合う非キャッシュメモリロケーションへのアクセスます

P6ファミリ・プロセッサ(及び新しい プロセッサ以降)は、次の追加メモリ 操作が常にアトミックに行われることを保証します。•

アンアラインドの16は、32は、 および64ビットは、キャッシュラインに収まるようにキャッシュされたメモリへのアクセス

このように、整列させることによって、1サイクルで読み込み可能で、1つのキャッシュラインに収まるため、読み込みアトミックになります。用

CMPXCHG8B説明:

ポインタがすでにロードされているので、コードが0x8048565に戻ってジャンプし、それが失敗した場合にCMPXCHG8Bは先の値にEAX:EDXを設定しますと、再びそれらをロードする必要はありませんIntel ISAマニュアルVol。 2A:

EDX:EAXとm64を比較してください。等しい場合は、ZFを設定し、ECX:EBXをm64にロードします。 それ以外の場合は、ZFをクリアし、m64をEDX:EAXにロードします。

したがって、コードでは、新たに返された値をインクリメントしてやり直す必要があります。 それの私たち、このCコード内の場合、それは容易になる:

value = dest; 
While(!CAS8B(&dest,value,value + 1)) 
{ 
    value = dest; 
} 
3

0x804855aと0x804855fにおける変数の読み取りがアトミックである必要はありません。擬似コードでは、このようなルックスをインクリメントする比較交換命令を使用する:

oldValue = *dest; 
do { 
    newValue = oldValue+1; 
} while (!compare_and_swap(dest, &oldValue, newValue)); 

比較交換は、スワップ前に、それは安全装置として機能します*dest == oldValueことを確認しているので - oldValue内の値であればそのようループが再試行されるため、アトミックでない読み取り結果が正しくない場合は問題ありません。

2番目の質問は、oldValue = *destがループ内にない理由です。これは、compare_and_swap関数が常にoldValueの値を実際の値の*destに置き換えるためです。だから本質的にあなたのために行oldValue = *destを実行し、それを再度行うことに意味がない。 cmpxchg8b命令の場合、比較が失敗した場合、メモリオペランドの内容はedx:eaxになります。compare_and_swapため

擬似コードは次のとおりです。

bool compare_and_swap (int *dest, int *oldVal, int newVal) 
{ 
    do atomically { 
    if (*oldVal == *dest) { 
     *dest = newVal; 
     return true; 
    } else { 
     *oldVal = *dest; 
     return false; 
    } 
    } 
} 

ところで、あなたのコードでは、あなたがvが64ビットに揃えていることを確認する必要があります - それ以外の場合は、2本のキャッシュ・ラインとcmpxchg8b命令意志の間で分割することができ原子的には実行されません。これにはGCCの__attribute__((aligned(8)))を使用できます。

関連する問題