簡體   English   中英

在具有獲取一致性與松散一致性的原子負載上自旋

[英]Spinning on an atomic load with acquire consistency vs. relaxed consistency

考慮下面的代碼:

// Class member initialization:
std::atomic<bool> ready_ = false;

...

// Core A:
while (!ready_.load(std::memory_order_acquire)) {
  // On x86, you would probably put a `pause` instruction here.
}
// Core A now accesses memory written by Core B.

...

// Core B:
// Core B writes memory.
ready_.store(true, std::memory_order_release);

假設核心 A 和核心 B 是兩個不同的物理核心(即,它們不是位於同一物理核心上的兩個超線程)。 核心 A 上面的代碼比下面的代碼性能更差還是性能相等? 請注意,Core A 只是在加載; 這不是涉及寫入的經典比較交換示例。 我對幾種架構的答案很感興趣。

// Core A:
while (!ready_.load(std::memory_order_relaxed)) {
  // On x86, you would probably put a `pause` instruction here.
}
std::atomic_thread_fence(std::memory_order_acquire);
// Core A now accesses memory written by Core B.

參考頁面上的郵箱代碼暗示底層代碼具有更好的性能,因為底層代碼避免了“不必要的同步”。 但是,郵箱代碼在許多原子上迭代,因此獲取一致性的同步開銷是一個問題,因為您可以使用寬松的一致性來避免對不屬於您的郵箱進行排序約束。 我不清楚在單個獲取負載上旋轉對性能有何影響。

有兩種方式可以使第一種代碼比第二種代碼效率低,至少在某些假設的架構上是這樣。 在 x86 上,我猜測它們會編譯成相同的代碼。

第一個問題是原子加載可能會影響其他處理器的性能。 在 alpha 上,這通常是研究 memory 一致性的一個很好的“異常值”案例,您會一遍又一遍地發出 memory 屏障指令,這可能會鎖定 memory 總線(在非 NUMA 機器上),或者做一些事情else 強制由另外兩個 CPU 寫入存儲的原子性。

第二個問題是屏障會影響所有先前的負載,而不僅僅是ready_的負載。 因此,也許在 NUMA 機器上, ready_實際上命中緩存,因為沒有爭用,並且您的 CPU 已經以獨占模式緩存它,但一些先前的負載正在等待 memory 系統。 現在您必須暫停 CPU 以等待之前的加載,而不是可能繼續執行與暫停的加載不沖突的指令。 這是一個例子:

int a = x.load(memory_order_relaxed);
while (!ready_.load(std::memory_order_relaxed))
  ;
std::atomic_thread_fence(std::memory_order_acquire);
int b = y;

在這種情況下, y的加載可能會停止等待x ,而如果ready_的加載已通過獲取語義完成,則x的加載可以繼續並行進行,直到需要該值為止。

對於第二個原因,您實際上可能想要以不同的方式構建自旋鎖。 以下是Erik Rigtorp建議如何在 x86 上實現自旋鎖,您可以輕松地適應您的用例:

  void lock() {
    for (;;) {
      if (!lock_.exchange(true, std::memory_order_acquire)) {
        break;
      }
      while (lock_.load(std::memory_order_relaxed)) {
        __builtin_ia32_pause();
      }
    }
  }

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM