簡體   English   中英

C ++ 11原子內存排序 - 這是放寬(釋放 - 消耗)排序的正確用法嗎?

[英]C++11 atomic memory ordering - is this a correct usage of relaxed (release-consume) ordering?

我最近使用std :: atomic三重緩沖區為C ++ 11創建了一個端口,用作並發同步機制。 這種線程同步方法背后的想法是,對於生產者 - 消費者情況,你有一個運行速度更快的生產者,消費者,三重緩沖可以帶來一些好處,因為生產者線程不會因為必須等待而“減慢”速度對於消費者。 在我的例子中,我有一個物理線程,在~120fps時更新,以及一個以~60fps運行的渲染線程。 顯然,我希望渲染線程始終能夠獲得最新狀態,但我也知道我將從物理線程中跳過很多幀,因為速率不同。 另一方面,我希望我的物理線程保持其不變的更新速率,而不受鎖定我的數據的較慢渲染線程的限制。

最初的C代碼是由remis-ideas制作的,完整的解釋在他的博客中 我鼓勵任何有興趣閱讀它的人進一步了解原始實現。

我的實現可以在這里找到。

基本思想是使一個具有3個位置(緩沖區)的數組和一個原子標志進行比較和交換,以定義在任何給定時間哪些數組元素對應於什么狀態。 這樣,只有一個原子變量用於模擬數組的所有3個索引和三重緩沖背后的邏輯。 緩沖區的3個位置被命名為Dirty,Clean和Snap。 生產者總是寫入Dirty索引,並且可以翻轉writer以將Dirty與當前的Clean索引交換。 消費者可以請求一個新的Snap,它將當前的Snap索引與Clean索引交換以獲得最新的緩沖區。 消費者總是在Snap位置讀取緩沖區。

該標志由8位無符號整數組成,這些位對應於:

(未使用)(新寫)(2x臟)(2x清潔)(2x快照)

newWrite extra bit標志由寫入器設置並由讀取器清除。 讀者可以使用它來檢查自上次捕捉以來是否有任何寫入,如果不是,則不會再次捕捉。 可以使用簡單的按位運算獲得標志和索引。

現在好了代碼:

template <typename T>
class TripleBuffer
{

public:

  TripleBuffer<T>();
  TripleBuffer<T>(const T& init);

  // non-copyable behavior
  TripleBuffer<T>(const TripleBuffer<T>&) = delete;
  TripleBuffer<T>& operator=(const TripleBuffer<T>&) = delete;

  T snap() const; // get the current snap to read
  void write(const T newT); // write a new value
  bool newSnap(); // swap to the latest value, if any
  void flipWriter(); // flip writer positions dirty / clean

  T readLast(); // wrapper to read the last available element (newSnap + snap)
  void update(T newT); // wrapper to update with a new element (write + flipWriter)

private:

  bool isNewWrite(uint_fast8_t flags); // check if the newWrite bit is 1
  uint_fast8_t swapSnapWithClean(uint_fast8_t flags); // swap Snap and Clean indexes
  uint_fast8_t newWriteSwapCleanWithDirty(uint_fast8_t flags); // set newWrite to 1 and swap Clean and Dirty indexes

  // 8 bit flags are (unused) (new write) (2x dirty) (2x clean) (2x snap)
  // newWrite   = (flags & 0x40)
  // dirtyIndex = (flags & 0x30) >> 4
  // cleanIndex = (flags & 0xC) >> 2
  // snapIndex  = (flags & 0x3)
  mutable atomic_uint_fast8_t flags;

  T buffer[3];
};

執行:

template <typename T>
TripleBuffer<T>::TripleBuffer(){

  T dummy = T();

  buffer[0] = dummy;
  buffer[1] = dummy;
  buffer[2] = dummy;

  flags.store(0x6, std::memory_order_relaxed); // initially dirty = 0, clean = 1 and snap = 2
}

template <typename T>
TripleBuffer<T>::TripleBuffer(const T& init){

  buffer[0] = init;
  buffer[1] = init;
  buffer[2] = init;

  flags.store(0x6, std::memory_order_relaxed); // initially dirty = 0, clean = 1 and snap = 2
}

template <typename T>
T TripleBuffer<T>::snap() const{

  return buffer[flags.load(std::memory_order_consume) & 0x3]; // read snap index
}

template <typename T>
void TripleBuffer<T>::write(const T newT){

  buffer[(flags.load(std::memory_order_consume) & 0x30) >> 4] = newT; // write into dirty index
}

template <typename T>
bool TripleBuffer<T>::newSnap(){

  uint_fast8_t flagsNow(flags.load(std::memory_order_consume));
  do {
    if( !isNewWrite(flagsNow) ) // nothing new, no need to swap
      return false;
  } while(!flags.compare_exchange_weak(flagsNow,
                                       swapSnapWithClean(flagsNow),
                                       memory_order_release,
                                       memory_order_consume));
  return true;
}

template <typename T>
void TripleBuffer<T>::flipWriter(){

  uint_fast8_t flagsNow(flags.load(std::memory_order_consume));
  while(!flags.compare_exchange_weak(flagsNow,
                                     newWriteSwapCleanWithDirty(flagsNow),
                                     memory_order_release,
                                     memory_order_consume));
}

template <typename T>
T TripleBuffer<T>::readLast(){
    newSnap(); // get most recent value
    return snap(); // return it
}

template <typename T>
void TripleBuffer<T>::update(T newT){
    write(newT); // write new value
    flipWriter(); // change dirty/clean buffer positions for the next update
}

template <typename T>
bool TripleBuffer<T>::isNewWrite(uint_fast8_t flags){
    // check if the newWrite bit is 1
    return ((flags & 0x40) != 0);
}

template <typename T>
uint_fast8_t TripleBuffer<T>::swapSnapWithClean(uint_fast8_t flags){
    // swap snap with clean
    return (flags & 0x30) | ((flags & 0x3) << 2) | ((flags & 0xC) >> 2);
}

template <typename T>
uint_fast8_t TripleBuffer<T>::newWriteSwapCleanWithDirty(uint_fast8_t flags){
    // set newWrite bit to 1 and swap clean with dirty 
    return 0x40 | ((flags & 0xC) << 2) | ((flags & 0x30) >> 2) | (flags & 0x3);
}

如您所見,我決定使用Release-Consume模式進行內存排序。 為存儲所述釋放 (memory_order_release)確保在當前線程沒有寫入可以在商店重新排序。 另一方面, Consume確保當前線程中的讀取不依賴於當前加載的值,可以此加載之前重新排序。 這確保了在當前線程中可以看到對釋放相同原子變量的其他線程中的因變量的寫入。

如果我的理解是正確的,因為我只需要原子設置標志,對其他不直接影響標志的變量的操作可以由編譯器自由重新排序,允許更多的優化。 通過閱讀新內存模型上的一些文檔,我也意識到這些輕松的原子只會對ARM和POWER等平台產生明顯的影響(主要是因為它們而引入)。 由於我的目標是ARM,我相信我可以從這些操作中受益,並能夠將性能提高一點。

現在提問:

我是否正確使用了Release-Consume這個特定問題的輕松排序?

謝謝,

安德烈

PS:對於長篇文章感到抱歉,但我認為需要一些不錯的背景來更好地了解問題。

編輯:實施@Yakk的建議:

  • newSnap()flipWriter()上讀取的固定flags使用直接賦值,因此使用默認load(std::memory_order_seq_cst)
  • 為清晰起見,將位操作移動到專用功能。
  • 添加bool返回類型到newSnap() ,現在返回false時沒有新的,否則為true。
  • 使用= delete idiom將類定義為不可復制的,因為如果使用TripleBuffer ,復制和賦值構造函數都是不安全的。

編輯2:修正了描述,這是不正確的(謝謝@Useless)。 消費者請求新的Snap並從Snap索引(而不是“writer”)讀取。 抱歉分心,感謝Useless指出來。

編輯3:根據@Display Name的建議優化newSnap()flipriter()函數,有效地在每個循環周期中刪除2個冗余load()

為什么要在CAS循環中加載舊標志值兩次? 第一次是flags.load() ,第二次是compare_exchange_weak() ,標准在CAS失敗時指定將第二個參數加載到第一個參數,在本例中是flagsNow。

根據http://en.cppreference.com/w/cpp/atomic/atomic/compare_exchange ,“ 否則,將存儲在* this中的實際值加載到預期值(執行加載操作)。 ”那么你的循環正在做的是失敗, compare_exchange_weak()重載flagsNow ,則循環重復,並且第一個語句加載它再次,通過負載后立即compare_exchange_weak() 在我看來,你的循環應該將負載拉出循環。 例如, newSnap()將是:

uint_fast8_t flagsNow(flags.load(std::memory_order_consume));
do
{
    if( !isNewWrite(flagsNow)) return false; // nothing new, no need to swap
} while(!flags.compare_exchange_weak(flagsNow, swapSnapWithClean(flagsNow), memory_order_release, memory_order_consume));

flipWriter()

uint_fast8_t flagsNow(flags.load(std::memory_order_consume));
while(!flags.compare_exchange_weak(flagsNow, newWriteSwapCleanWithDirty(flagsNow), memory_order_release, memory_order_consume));

是的,它是memory_order_acquire和memory_order_consume之間的區別,但是當你每秒使用180左右時你不會注意到它。 如果你想知道數字的答案,你可以使用m2 = memory_order_consume運行我的測試。 只需將producer_or_consumer_Thread更改為:

TripleBuffer <int> tb;

void producer_or_consumer_Thread(void *arg)
{
    struct Arg * a = (struct Arg *) arg;
    bool succeeded = false;
    int i = 0, k, kold = -1, kcur;

    while (a->run)
    {
        while (a->wait) a->is_waiting = true; // busy wait
        if (a->producer)
        {
            i++;
            tb.update(i);
            a->counter[0]++;
        }
        else
        {
            kcur = tb.snap();
            if (kold != -1 && kcur != kold) a->counter[1]++;
            succeeded = tb0.newSnap();
            if (succeeded)
            {
                k = tb.readLast();
                if (kold == -1)
                    kold = k;
                else if (kold = k + 1)
                    kold = k;
                else
                    succeeded = false;
            }
            if (succeeded) a->counter[0]++;   
        }
    }
    a->is_waiting =  true;
}

測試結果:

_#_  __Produced __Consumed _____Total
  1    39258150   19509292   58767442
  2    24598892   14730385   39329277
  3    10615129   10016276   20631405
  4    10617349   10026637   20643986
  5    10600334    9976625   20576959
  6    10624009   10069984   20693993
  7    10609040   10016174   20625214
  8    25864915   15136263   41001178
  9    39847163   19809974   59657137
 10    29981232   16139823   46121055
 11    10555174    9870567   20425741
 12    25975381   15171559   41146940
 13    24311523   14490089   38801612
 14    10512252    9686540   20198792
 15    10520211    9693305   20213516
 16    10523458    9720930   20244388
 17    10576840    9917756   20494596
 18    11048180    9528808   20576988
 19    11500654    9530853   21031507
 20    11264789    9746040   21010829

暫無
暫無

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

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