Upload
tatsuki-shimizu
View
409
Download
0
Embed Size (px)
Citation preview
Effective Modern C++#9Effective Modern C++#92015/09/30@simizut22
item40item40並⾏行処理には並⾏行処理には atomic atomicをを特殊メモリには特殊メモリには volatile volatile をを
volatile volatile が並⾏行プログラムが並⾏行プログラムで役に⽴立たない理由で役に⽴立たない理由11
volatile int vc{0}; // volatile counter std::atomic< int > ac{0}; // atomic counter
void f() { ++vc; // invrement vc ++ac; // atomically increment }
single-thread single-thread でで((1度だけ1度だけ) ) 呼ぶなら,呼ぶなら,vc, ac vc, ac とともにもに 1 1 だがだが......
n-thread n-thread から呼び出したらどうなるから呼び出したらどうなる????
変数 値
ac 2
vc ????
注意: バグらせるために若干コードを修正しています
http://melpon.org/wandbox/permlink/a8USjwKXZ3uiVXpC
thread 1 thread 21 vc の読み取り2 vc の読み取り3 vc に書き込み4 vc に書き込み
例えば例えば......
と実⾏行された場合はと実⾏行された場合は 1 1 になる.になる.((実際には実際には UB) UB)
アトミック操作はスレッド間でデータをやり取りするアトミック操作はスレッド間でデータをやり取りするための最も基本的な同期プリミティブであり、ための最も基本的な同期プリミティブであり、変数への不可分変数への不可分(atomic)(atomic)な読み込みな読み込み//書き込み書き込み//読み書き読み書きを同時に⾏行う操作を同時に⾏行う操作(Read-Modify-Write operation)(Read-Modify-Write operation)を提供する.を提供する. [[引⽤用引⽤用]]cpprefjp:atomiccpprefjp:atomic
atomic atomic
iso iso が定める範囲のが定める範囲の volatile volatile にはこのような機能は定にはこのような機能は定義されていない義されていない
* ただし, implementation-defined なので,独⾃自拡張も存在
例: msvchttps://msdn.microsoft.com/en-us/library/12a04hfd.aspx
補⾜足:補⾜足:CC標準の標準のvolatilevolatile
volatile: volatile: 処理系が制御できない⽅方法で変数が変更され処理系が制御できない⽅方法で変数が変更されうることを処理系に伝えるうることを処理系に伝える
⇒⇒副作⽤用完了点を跨いでの最適化に関与すべきでない副作⽤用完了点を跨いでの最適化に関与すべきでない* * ⼆二つの副作⽤用完了点の間で⾏行うのは問題ない⼆二つの副作⽤用完了点の間で⾏行うのは問題ない
1. atomic 1. atomic 性がないから性がないから
volatile volatile が並⾏行プログラムが並⾏行プログラムで役に⽴立たない理由で役に⽴立たない理由22
std::atomic std::atomic の使いどこの使いどこ /* 何がしかの修飾 */ bool valAvailable{ false }; auto value = compute(); // valAvailable = true;
[出典] : wikipedia(考える⼈人)
計算した結果が set されていることを表す flag なんだな
a = b; // (1) x = y; // (2)
x = y; // (2) a = b; // (1)
compiler 次の並び替えを⾏行っても良いという⾃自由がある
並び替え前
並び替え後
注意:バグらせるためにわざと順番を変えています
http://melpon.org/wandbox/permlink/P2ryaRilsOzDLCMN
http://melpon.org/wandbox/permlink/ZpldRfPmz3cFvljY
先の例に当てはめると次みたいな感じ
std::atomic std::atomic のの "default" "default"では、では、上記並び替えが抑制される上記並び替えが抑制される
std::atomic< bool > valAvailable{ false }; auto value = compute(); // valAvailable = true;
つまり、atomic で宣⾔言しておけばひとまず上の問題にはならない
// defined in <atomic> header
typedef enum memory_order { memory_order_relaxed, memory_order_consume, memory_order_acquire, memory_order_release, memory_order_acq_rel, memory_order_seq_cst } memory_order;
補⾜足補⾜足: memory-order: memory-order
で、定義される列挙型
atomic object のメンバー関数の default は seq_cst になっている(seq_cst は sequential_consitency の略)
enum 値 意味
relaxed 順序付けの効果はないacquire 後続の命令が acquire 指定の命令より前にリオーダーさ
れないことを保証する(読み込みでのみ使⽤用)
consume acquire が 後続すべての命令に対して順序をつけるのに、対し consume は読み込みの値に依存する操作のみに順序をつける
release 先⾏行する命令が release 指定の命令より後にリオーダーされないことを保証する(書き込みでのみ使⽤用)
acq_rel acquire と release を合わせた効果(RMWに対してのみ使⽤用可)
seq_cst acquire/release/acq_rel すべての効果を持つ。かつ、すべてのスレッドから⾒見て⼀一貫性を保証する
cpprefjp:memory_order
volatile volatile にはそのような効にはそのような効果はない!!果はない!!
(iso (iso で定める範囲ではで定める範囲では))
(*)msvc だと1. volatile object への書き込みは release semantics を持つ2. volatile object への書き込みは acquire semantics を持つらしい(ただし ARM でなく, /volatile:iso 指定がされていない時)
volatile volatile が並⾏行プログラムで役に⽴立が並⾏行プログラムで役に⽴立たない理由たない理由((まとめまとめ))
1. atomic 1. atomic 性がない性がない
2. 2. コードの並び替えの抑制をしないコードの並び替えの抑制をしない
いついつ volatile volatile が有⽤用かが有⽤用か????
int x{0};
int fun() { auto y = x; y = x; x = 10; x = 20; return y; }
⼿手元の msvc14.0 (/O2 /volatile:iso option)で assymbly-out するとこんな感じ (fun のとこだけ)
?fun@@YAHXZ PROC ; fun, COMDAT ; Line 5 mov eax, DWORD PTR ?x@@3HA ; x ; Line 6 mov DWORD PTR ?x@@3HA, 20 ; x ; Line 8 ret 0 ?fun@@YAHXZ ENDP ; fun
つまり、これと⼀一緒になったつまり、これと⼀一緒になった
int x{0}; // global data
int fun() { auto y = x; x = 20; return y; }
メモリがメモリが""通常通り通常通り""振る舞う振る舞う↓↓
この⼿手の最適化は有効だけどこの⼿手の最適化は有効だけど……
特殊なメモリに対してやられては困る特殊なメモリに対してやられては困る例例))
- - コンピュータハードウェアコンピュータハードウェア - - 割り込みハンドラ割り込みハンドラ - memory-mapped IO - memory-mapped IO
のような⾮非同期プロセスが使うメモリにのような⾮非同期プロセスが使うメモリに((信頼性のあ信頼性のあるる))アクセスをするときなどアクセスをするときなど
アドレス ⽤用途 program 側でできる0xFFFFFF20 ⼊入⼒力データバッファ 読めるけど書けない0xFFFFFF24 出⼒力データバッファ 書けるけど読めない0xFFFFFF28 制御レジスタ 読めるけど書けない
sample) あるコンピュータに3つの特別な hardware location が存在
制御レジスタ制御レジスタ(0xFFFFFF28)(0xFFFFFF28)::第第 3 LSB bit : 3 LSB bit : ⼊入⼒力完了⼊入⼒力完了第第 2 LSB bit : 2 LSB bit : 出⼒力可能出⼒力可能
を表すとするを表すとする
counttype copy_data() { counttype count = 0; datatype tmp; for (;;) { /* wait for input data */ while (!input_ready) { } tmp = *INPUT_BUF; if (tmp == 0) { return count; } /* wait for output_is_ready */ while (!output_ready) {} *OUTPUT_BUF = tmp; count++; } }
copy_data - 0 が⼊入⼒力されるまで⼊入⼒力から出⼒力へコピー - ⽂文字数を返す
参考:「C リファレンスマニュアル~第 5 版~」p101~103
typedef unsigned long datatype, controltype, counttype; #define INPUT_BUF \ ((const volatile datatype * const) 0xFFFFFF20) \ // #define OUTPUT_BUF \ ((volatile datatype * const) 0xFFFFFF24) \ // #define CONTROLLER \ ((const volatile controltype * const) 0xFFFFFF28) \ // #define input_ready \ ((*CONTROLLER) & 0x4) \ // #define output_ready \ ((*CONTROLLER) & 0x2) \ //
上の define で volatile を使うことで、特殊なメモリを使っていることを教えている
最初の例に戻って最初の例に戻って
volatile int x{0};
int fun() { auto y = x; // decltype(y) = int y = x; x = 10; x = 20; return y; }
?fun@@YAHXZ PROC ; fun, COMDAT ; File ; Line 4 mov eax, DWORD PTR ?x@@3HC ; x ; Line 5 mov eax, DWORD PTR ?x@@3HC ; x ; Line 6 mov DWORD PTR ?x@@3HC, 10 ; x ; Line 7 mov DWORD PTR ?x@@3HC, 20 ; x ; Line 9 ret 0 ?fun@@YAHXZ ENDP ; fun
再び assembly-out してみる
👆簡約されていないことがわかる
特殊なメモリを扱うときに特殊なメモリを扱うときに volatile volatile が役に⽴立つが役に⽴立つ( ( というかそれ以外の意味が本来はない。。。というかそれ以外の意味が本来はない。。。))
atomic atomic はは volatile volatile の代わりになるかの代わりになるか????
x x をを volatile int volatile int →→ std::atomic< int > std::atomic< int > に変えるに変える
まずは愚直に…
std::atomic< int > x{0};
int fun() { auto y = x; // error!! copy ctor is deleted y = x; // error!! copy assign is deleted x = 10; x = 20; return y; }
http://melpon.org/wandbox/permlink/ld7zvejwv3KAahkn
atomic の copy ctor/assign が deleted のため compile error
修正する
std::atomic< int > x{0};
int fun() { // auto y = x; // error!! copy ctor is deleted int reg = x.load(); std::atomic< int > y{ reg }; // y = x; // error!! copy assign is deleted reg = x.load(); y = reg; x = 10; x = 20; return y; }
http://melpon.org/wandbox/permlink/W2dqqMZ0KLXyplVQ
?fun@@YAHXZ PROC ; fun, COMDAT ; File ; Line 8 mov eax, DWORD PTR ?x@@3U?$atomic@H@std@@A ; x ; Line 9 mov DWORD PTR y$[rsp], eax ; Line 10 mov eax, DWORD PTR ?x@@3U?$atomic@H@std@@A ; x ; Line 11 xchg DWORD PTR y$[rsp], eax ; Line 12 mov eax, 10 ; Line 13 mov ecx, 20 xchg DWORD PTR ?x@@3U?$atomic@H@std@@A, eax ; x xchg DWORD PTR ?x@@3U?$atomic@H@std@@A, ecx ; x ; Line 14 mov eax, DWORD PTR y$[rsp] ; Line 15 ret 0 ?fun@@YAHXZ ENDP ; fun
((・・__・・;) ;) あれあれ????
テキスト本⽂文テキスト本⽂文
std::atomic< int > y(x.load()); x = 20;
になるはずとか⾔言ってるけどならないになるはずとか⾔言ってるけどならない……(clang3.8/gcc5.2 (clang3.8/gcc5.2 でも同様でも同様))
注注: y : y ののatomic atomic なな copy copy は怪しいのでそこを省いて、は怪しいのでそこを省いて、 y y なしにしてもならなかったなしにしてもならなかった
とにかくとにかくstd::atomic std::atomic に特殊メモリを扱う機能はないに特殊メモリを扱う機能はない
↓↓atomic atomic でも特殊メモリ使いたいならでも特殊メモリ使いたいなら
volatile std::atomic< int > vai;
とと volatile volatile とと atomic atomic を併⽤用するを併⽤用する
参考:yohhoyの⽇日記~volatile版atomic操作関数が存在する理由~
例えば、例えば、 fetch_add fetch_add であればであれば
namespace std { template <class T> T atomic_fetch_add(volatile atomic<T>* object, T operand) noexcept;
template <class T> T atomic_fetch_add(atomic<T>* object, T operand) noexcept; }
となっており、となっており、atomic atomic にに 特殊メモリを扱うことが可能特殊メモリを扱うことが可能
Things to RememberThings to Remember
std::atomic std::atomic はは mutex mutex を⽤用いないで複数スレッドかを⽤用いないで複数スレッドからアクセスできるデータを表現するらアクセスできるデータを表現する
volatile volatile はは, (, (読み取り読み取り//書き込みを最適化すべきはな書き込みを最適化すべきはないい) ) 特殊なメモリを表現する特殊なメモリを表現する(compiler (compiler に教えるに教える))