目錄

廣告 AD

Atomic Operation 是甚麼?原子操作?

剛好在研究 Atomic Operation

因此整理了一下常見的 Atomic Operation

廣告 AD

Atomic Operation 代表這個操作是最小的操作,也就是說,當電腦在執行操作時,不會有其他人打斷這個操作,一次就全部完成。

接下來就來介紹各種不同的 Atomic Operation 吧!

Atomic Operation 如果直接翻譯的話是原子操作啦,但好像翻成最小操作比較直覺(?)


會先比較變數「當前的值」和「期望的值」是否相同,如果相同,則寫入「新的數值」。

cpp

void compare_and_swap(int* var, int expect_val, int new_val){
  if(*var == expect_val){
    *var = new_val;
  }
}

GCC 內建有提供 CAS 的 function,共有兩種:

  • __sync_bool_compare_and_swap:回傳值是 bool,如果有成功被修改為 newval,回傳 true,反之則回傳 false
  • __sync_val_compare_and_swap:回傳 *ptr 在操作執行前的數值。

cpp

bool __sync_bool_compare_and_swap (type *ptr, type oldval type newval, ...)
type __sync_val_compare_and_swap (type *ptr, type oldval type newval, ...)

GCC 在 4.7.0 版本後,依據 C++11 的 memory model 有對 Atomic operator 更新1,但舊版的還是可以用。

更新後加入了成功和失敗的時候所採用的 memory order,如果不知道的話就用 __ATOMIC_SEQ_CST

另外也加入了 weak 和 strong 的類型,weak 的版本可能會 fail spuriously,但速度較快,相反地,strong 的版本不會發生 fail spuriously,但速度較慢,根據 GCC 文件,很多平台只提供 strong 的類型,所以如果不知道要用甚麼就用 strong 吧。

fail spuriously:即便變數當前的數值和期望的一致,仍然有機會回傳 false,這可能與實作方式有關,例如有些架構上使用 LL/SC 來實作 CAS,在 context switches 或是 cache flush 後,SC 依然有可能認為 LL 的變數有被動過,因此就會回傳 false。

__atomic_compare_exchange_n__atomic_compare_exchange 的差別只在 desired 是不是指標。

cpp

bool __atomic_compare_exchange_n (type *ptr, type *expected, type desired, bool weak, int success_memorder, int failure_memorder)
bool __atomic_compare_exchange (type *ptr, type *expected, type *desired, bool weak, int success_memorder, int failure_memorder)

C++11 標準中也有 CAS,在 <atomic>,其中分別有 weak 和 strong 的版本,explicit 則是有明確指定 memory order,沒有明確指定的就是 memory_order_seq_cst

cpp

atomic_compare_exchange_weak
atomic_compare_exchange_weak_explicit
atomic_compare_exchange_strong
atomic_compare_exchange_strong_explicit	

LL/SC 是兩個 CPU 指令,就是 Load Link (LL) 和 Store Conditional (SC)。

LL 會從記憶體讀取目標資料到暫存器,也就是得到了目標的當前值,SC 會將新的數值寫回原本的目標,如果該目標從執行 LL 到 SC 之間沒有任何修改的話。

在 MIPS 中有提供 LL/SC。


TSL 會將數值設定為 set (非 0 數值),並回傳操作前的舊值 (0 or 非 0 數值),用 C++ 寫的話就是如下:

cpp

int test_and_set(int* var){
  int old_val = *var;
  *var = 1;
  return old_val;
}

使用 Test and Set 可以輕鬆地實作 spin lock,在一些等待時間不長的情況下,可以避免 content switch 所帶來的成本。

舊版的 GCC 函式中是根據 Intel Itanium Processor-specific Application Binary Interface 所訂製的,裡面沒有這邊描述的 Test and Set2,因此可以用 CAS 模擬:

text

#define __sync_test_and_set __sync_val_compare_and_swap(ptr, 0, 1)

更新過的函式,一樣支持了 memory order,如果原本就是 set (非 0 數值),則回傳 true,反之則是 false

cpp

bool __atomic_test_and_set (void *ptr, int memorder)

C++11 標準中在 <atomic> 也有提供 TSL,explicit 則是有明確指定 memory order,沒有明確指定的就是 memory_order_seq_cst

cpp

atomic_flag_test_and_set
atomic_flag_test_and_set_explicit

將目標數值加上新的數值,如果用 C++ 寫起來就是以下的樣子:

cpp

int fetch_and_add(int* var, int val){
  int old_val = *var;
  *var += val;
  return old_val;
}

如果你的操作只是對原本的數值做一些操作,則直接使用 FAA 會比較有效率,畢竟不用 CAS + 迴圈。

舊版的 GCC 函式有提供除了 Add 之外的操作,像是 add、sub、and…,回傳操作前的舊值:

cpp

type __sync_fetch_and_add (type *ptr, type value, ...)
type __sync_fetch_and_sub (type *ptr, type value, ...)
type __sync_fetch_and_or (type *ptr, type value, ...)
type __sync_fetch_and_and (type *ptr, type value, ...)
type __sync_fetch_and_xor (type *ptr, type value, ...)
type __sync_fetch_and_nand (type *ptr, type value, ...)

上方的是回傳舊值,GCC 也提供回傳新值的:

cpp

type __sync_add_and_fetch (type *ptr, type value, ...)
type __sync_sub_and_fetch (type *ptr, type value, ...)
type __sync_or_and_fetch (type *ptr, type value, ...)
type __sync_and_and_fetch (type *ptr, type value, ...)
type __sync_xor_and_fetch (type *ptr, type value, ...)
type __sync_nand_and_fetch (type *ptr, type value, ...)

更新後的版本也有提供 FAA 和 memory order:

cpp

type __atomic_fetch_add (type *ptr, type val, int memorder)
type __atomic_fetch_sub (type *ptr, type val, int memorder)
type __atomic_fetch_and (type *ptr, type val, int memorder)
type __atomic_fetch_xor (type *ptr, type val, int memorder)
type __atomic_fetch_or (type *ptr, type val, int memorder)
type __atomic_fetch_nand (type *ptr, type val, int memorder)

上方的是回傳舊值,下方回傳新值的:

cpp

type __atomic_add_fetch (type *ptr, type val, int memorder)
type __atomic_sub_fetch (type *ptr, type val, int memorder)
type __atomic_and_fetch (type *ptr, type val, int memorder)
type __atomic_xor_fetch (type *ptr, type val, int memorder)
type __atomic_or_fetch (type *ptr, type val, int memorder)
type __atomic_nand_fetch (type *ptr, type val, int memorder)

C++11 標準中在 <atomic> 也有提供 FAA 和其他相關的操作,explicit 則是有明確指定 memory order,沒有明確指定的就是 memory_order_seq_cst

cpp

atomic_fetch_add
atomic_fetch_add_explicit
atomic_fetch_sub
atomic_fetch_sub_explicit
atomic_fetch_and
atomic_fetch_and_explicit
atomic_fetch_or
atomic_fetch_or_explicit
atomic_fetch_xor
atomic_fetch_xor_explicit

廣告 AD