1. ホーム
  2. c++

[解決済み] std::atomicのロックはどこにありますか?

2023-08-26 10:35:01

質問

データ構造に複数の要素がある場合、そのアトミックなバージョンは (常に) ロックフリーであることはできません。 これは、CPU が何らかのロックを使用せずにデータをアトミックに変更できないため、より大きな型に当てはまると聞きました。

たとえば

#include <iostream>
#include <atomic>

struct foo {
    double a;
    double b;
};

std::atomic<foo> var;

int main()
{
    std::cout << var.is_lock_free() << std::endl;
    std::cout << sizeof(foo) << std::endl;
    std::cout << sizeof(var) << std::endl;
}

という出力があります(Linux/gcc)。

0
16
16

アトミックと foo は同じサイズなので、atomicにロックが格納されているとは思えません。

質問なのですが

アトム変数がロックを使用する場合、それはどこに格納され、その変数の複数のインスタンスのために何を意味するのでしょうか?

どのように解決するのですか?

このような質問に答える最も簡単な方法は、一般に、出来上がったアセンブリを見て、そこから判断することです。

次のようにコンパイルします (コンパイラの悪巧みをかわすために、構造体を大きくしました)。

#include <atomic>

struct foo {
    double a;
    double b;
    double c;
    double d;
    double e;
};

std::atomic<foo> var;

void bar()
{
    var.store(foo{1.0,2.0,1.0,2.0,1.0});
}

clang 5.0.0では、-O3のもとで以下のようになります。 godboltで見る

bar(): # @bar()
  sub rsp, 40
  movaps xmm0, xmmword ptr [rip + .LCPI0_0] # xmm0 = [1.000000e+00,2.000000e+00]
  movaps xmmword ptr [rsp], xmm0
  movaps xmmword ptr [rsp + 16], xmm0
  movabs rax, 4607182418800017408
  mov qword ptr [rsp + 32], rax
  mov rdx, rsp
  mov edi, 40
  mov esi, var
  mov ecx, 5
  call __atomic_store

素晴らしいことに、コンパイラは組込み関数に委ねる ( __atomic_store ) に委譲しているのですが、これでは何が本当に起こっているのかわかりません。しかし、このコンパイラはオープンソースであるため、組込み関数の実装を簡単に見つけることができます。 https://github.com/llvm-mirror/compiler-rt/blob/master/lib/builtins/atomic.c ):

void __atomic_store_c(int size, void *dest, void *src, int model) {
#define LOCK_FREE_ACTION(type) \
    __c11_atomic_store((_Atomic(type)*)dest, *(type*)dest, model);\
    return;
  LOCK_FREE_CASES();
#undef LOCK_FREE_ACTION
  Lock *l = lock_for_pointer(dest);
  lock(l);
  memcpy(dest, src, size);
  unlock(l);
}

でマジックが起こるようです。 lock_for_pointer() にあるようなので、見てみましょう。

static __inline Lock *lock_for_pointer(void *ptr) {
  intptr_t hash = (intptr_t)ptr;
  // Disregard the lowest 4 bits.  We want all values that may be part of the
  // same memory operation to hash to the same value and therefore use the same
  // lock.  
  hash >>= 4;
  // Use the next bits as the basis for the hash
  intptr_t low = hash & SPINLOCK_MASK;
  // Now use the high(er) set of bits to perturb the hash, so that we don't
  // get collisions from atomic fields in a single object
  hash >>= 16;
  hash ^= low;
  // Return a pointer to the word to use
  return locks + (hash & SPINLOCK_MASK);
}

そして、ここからが説明です。アトミックのアドレスは、事前に割り当てられたロックを選択するためのハッシュキーを生成するために使用されます。