[英]what is the difference between ++, add operation and fetch_add() in atomic()
[英]atomic fetch_add vs add performance
下面的代碼演示了多線程編程的好奇心。 特別是在單個線程中std::memory_order_relaxed
增量與常規增量的性能。 我不明白為什么fetch_add(寬松)單線程比常規增量慢兩倍。
static void BM_IncrementCounterLocal(benchmark::State& state) {
volatile std::atomic_int val2;
while (state.KeepRunning()) {
for (int i = 0; i < 10; ++i) {
DoNotOptimize(val2.fetch_add(1, std::memory_order_relaxed));
}
}
}
BENCHMARK(BM_IncrementCounterLocal)->ThreadRange(1, 8);
static void BM_IncrementCounterLocalInt(benchmark::State& state) {
volatile int val3 = 0;
while (state.KeepRunning()) {
for (int i = 0; i < 10; ++i) {
DoNotOptimize(++val3);
}
}
}
BENCHMARK(BM_IncrementCounterLocalInt)->ThreadRange(1, 8);
輸出:
Benchmark Time(ns) CPU(ns) Iterations ---------------------------------------------------------------------- BM_IncrementCounterLocal/threads:1 59 60 11402509 BM_IncrementCounterLocal/threads:2 30 61 11284498 BM_IncrementCounterLocal/threads:4 19 62 11373100 BM_IncrementCounterLocal/threads:8 17 62 10491608 BM_IncrementCounterLocalInt/threads:1 31 31 22592452 BM_IncrementCounterLocalInt/threads:2 15 31 22170842 BM_IncrementCounterLocalInt/threads:4 8 31 22214640 BM_IncrementCounterLocalInt/threads:8 9 31 21889704
對於volatile int
,編譯器必須確保它不會優化和/或重新排序變量的任何讀/寫。
使用fetch_add
, CPU必須采取fetch_add
讀取 - 修改 - 寫入操作是原子的。
這兩個完全不同的要求:原子性要求意味着CPU必須與機器上的其他CPU通信,確保它們不會在自己的讀寫之間讀/寫給定的內存位置。 如果編譯器使用比較和交換指令編譯fetch_add
,它實際上會發出一個短循環來捕獲其他CPU修改其中的值的情況。
對於volatile int
不需要這樣的通信。 相反, volatile
要求編譯器不發明任何讀取: volatile
設計用於與硬件寄存器的單線程通信,其中僅僅讀取值的行為可能具有副作用。
本地版本不使用原子。 (它使用volatile
的事實是一個紅色的鯡魚 - volatile
在多線程代碼中基本沒有意義)。
原子版本使用原子能公司(!)。 事實上,只有一個線程實際上將用於訪問變量,這對CPU來說是不可見的,我也不會驚訝於編譯器也沒有發現它。 (沒有必要浪費開發人員的努力來確定將std::atomic_int
轉換為int
是否安全,當它幾乎永遠不會出現時。如果不需要從多個線程訪問它,則不會編寫atomic_int
。)
因此,原子版本將難以確保增量實際上是原子的,坦率地說,我感到驚訝的是它只慢了2倍 - 我本來期望更像是10倍。
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.