36
Effective Modern C++#9 Effective Modern C++#9 2015/09/30 @simizut22

Effective modern-c++#9

Embed Size (px)

Citation preview

Page 1: Effective modern-c++#9

Effective Modern C++#9Effective Modern C++#92015/09/30@simizut22

Page 2: Effective modern-c++#9

item40item40並⾏行処理には並⾏行処理には atomic atomicをを特殊メモリには特殊メモリには volatile volatile をを

Page 3: Effective modern-c++#9

volatile volatile が並⾏行プログラムが並⾏行プログラムで役に⽴立たない理由で役に⽴立たない理由11

Page 4: Effective modern-c++#9

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 から呼び出したらどうなるから呼び出したらどうなる????

Page 5: Effective modern-c++#9

変数 値

ac 2

vc ????

注意: バグらせるために若干コードを修正しています

http://melpon.org/wandbox/permlink/a8USjwKXZ3uiVXpC

Page 6: Effective modern-c++#9

thread 1 thread 21 vc の読み取り2 vc の読み取り3 vc に書き込み4 vc に書き込み

例えば例えば......

と実⾏行された場合はと実⾏行された場合は 1 1 になる.になる.((実際には実際には UB) UB)

Page 7: Effective modern-c++#9

アトミック操作はスレッド間でデータをやり取りするアトミック操作はスレッド間でデータをやり取りするための最も基本的な同期プリミティブであり、ための最も基本的な同期プリミティブであり、変数への不可分変数への不可分(atomic)(atomic)な読み込みな読み込み//書き込み書き込み//読み書き読み書きを同時に⾏行う操作を同時に⾏行う操作(Read-Modify-Write operation)(Read-Modify-Write operation)を提供する.を提供する. [[引⽤用引⽤用]]cpprefjp:atomiccpprefjp:atomic

atomic atomic

Page 8: Effective modern-c++#9

iso iso が定める範囲のが定める範囲の volatile volatile にはこのような機能は定にはこのような機能は定義されていない義されていない

* ただし, implementation-defined なので,独⾃自拡張も存在

例: msvchttps://msdn.microsoft.com/en-us/library/12a04hfd.aspx

Page 9: Effective modern-c++#9

補⾜足:補⾜足:CC標準の標準のvolatilevolatile

volatile: volatile: 処理系が制御できない⽅方法で変数が変更され処理系が制御できない⽅方法で変数が変更されうることを処理系に伝えるうることを処理系に伝える

⇒⇒副作⽤用完了点を跨いでの最適化に関与すべきでない副作⽤用完了点を跨いでの最適化に関与すべきでない* * ⼆二つの副作⽤用完了点の間で⾏行うのは問題ない⼆二つの副作⽤用完了点の間で⾏行うのは問題ない

Page 10: Effective modern-c++#9

1. atomic 1. atomic 性がないから性がないから

Page 11: Effective modern-c++#9

volatile volatile が並⾏行プログラムが並⾏行プログラムで役に⽴立たない理由で役に⽴立たない理由22

Page 12: Effective modern-c++#9

std::atomic std::atomic の使いどこの使いどこ /* 何がしかの修飾 */ bool valAvailable{ false }; auto value = compute(); // valAvailable = true;

[出典] : wikipedia(考える⼈人)

計算した結果が set されていることを表す flag なんだな

Page 13: Effective modern-c++#9

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

先の例に当てはめると次みたいな感じ

Page 14: Effective modern-c++#9

std::atomic std::atomic のの "default" "default"では、では、上記並び替えが抑制される上記並び替えが抑制される

std::atomic< bool > valAvailable{ false }; auto value = compute(); // valAvailable = true;

つまり、atomic で宣⾔言しておけばひとまず上の問題にはならない

Page 15: Effective modern-c++#9

// 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 の略)

Page 16: Effective modern-c++#9

enum 値 意味

relaxed 順序付けの効果はないacquire 後続の命令が acquire 指定の命令より前にリオーダーさ

れないことを保証する(読み込みでのみ使⽤用)

consume acquire が 後続すべての命令に対して順序をつけるのに、対し consume は読み込みの値に依存する操作のみに順序をつける

release 先⾏行する命令が release 指定の命令より後にリオーダーされないことを保証する(書き込みでのみ使⽤用)

acq_rel acquire と release を合わせた効果(RMWに対してのみ使⽤用可)

seq_cst acquire/release/acq_rel すべての効果を持つ。かつ、すべてのスレッドから⾒見て⼀一貫性を保証する

cpprefjp:memory_order

Page 17: Effective modern-c++#9

volatile volatile にはそのような効にはそのような効果はない!!果はない!!

(iso (iso で定める範囲ではで定める範囲では))

(*)msvc だと1. volatile object への書き込みは release semantics を持つ2. volatile object への書き込みは acquire semantics を持つらしい(ただし ARM でなく, /volatile:iso 指定がされていない時)

Page 18: Effective modern-c++#9

volatile volatile が並⾏行プログラムで役に⽴立が並⾏行プログラムで役に⽴立たない理由たない理由((まとめまとめ))

1. atomic 1. atomic 性がない性がない

2. 2. コードの並び替えの抑制をしないコードの並び替えの抑制をしない

Page 19: Effective modern-c++#9

いついつ volatile volatile が有⽤用かが有⽤用か????

Page 20: Effective modern-c++#9

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

Page 21: Effective modern-c++#9

つまり、これと⼀一緒になったつまり、これと⼀一緒になった

int x{0}; // global data

int fun() { auto y = x; x = 20; return y; }

メモリがメモリが""通常通り通常通り""振る舞う振る舞う↓↓

この⼿手の最適化は有効だけどこの⼿手の最適化は有効だけど……

Page 22: Effective modern-c++#9

特殊なメモリに対してやられては困る特殊なメモリに対してやられては困る例例))

- - コンピュータハードウェアコンピュータハードウェア - - 割り込みハンドラ割り込みハンドラ - memory-mapped IO - memory-mapped IO

のような⾮非同期プロセスが使うメモリにのような⾮非同期プロセスが使うメモリに((信頼性のあ信頼性のあるる))アクセスをするときなどアクセスをするときなど

Page 23: Effective modern-c++#9

アドレス ⽤用途 program 側でできる0xFFFFFF20 ⼊入⼒力データバッファ 読めるけど書けない0xFFFFFF24 出⼒力データバッファ 書けるけど読めない0xFFFFFF28 制御レジスタ 読めるけど書けない

sample) あるコンピュータに3つの特別な hardware location が存在

制御レジスタ制御レジスタ(0xFFFFFF28)(0xFFFFFF28)::第第 3 LSB bit : 3 LSB bit : ⼊入⼒力完了⼊入⼒力完了第第 2 LSB bit : 2 LSB bit : 出⼒力可能出⼒力可能

を表すとするを表すとする

Page 24: Effective modern-c++#9

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

Page 25: Effective modern-c++#9

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 を使うことで、特殊なメモリを使っていることを教えている

Page 26: Effective modern-c++#9

最初の例に戻って最初の例に戻って

volatile int x{0};

int fun() { auto y = x; // decltype(y) = int y = x; x = 10; x = 20; return y; }

Page 27: Effective modern-c++#9

?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 してみる

👆簡約されていないことがわかる

Page 28: Effective modern-c++#9

特殊なメモリを扱うときに特殊なメモリを扱うときに volatile volatile が役に⽴立つが役に⽴立つ( ( というかそれ以外の意味が本来はない。。。というかそれ以外の意味が本来はない。。。))

Page 29: Effective modern-c++#9

atomic atomic はは volatile volatile の代わりになるかの代わりになるか????

Page 30: Effective modern-c++#9

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

Page 31: Effective modern-c++#9

修正する

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

Page 32: Effective modern-c++#9

?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

((・・__・・;) ;) あれあれ????

Page 33: Effective modern-c++#9

テキスト本⽂文テキスト本⽂文

std::atomic< int > y(x.load()); x = 20;

になるはずとか⾔言ってるけどならないになるはずとか⾔言ってるけどならない……(clang3.8/gcc5.2 (clang3.8/gcc5.2 でも同様でも同様))

注注: y : y ののatomic atomic なな copy copy は怪しいのでそこを省いて、は怪しいのでそこを省いて、 y y なしにしてもならなかったなしにしてもならなかった

Page 34: Effective modern-c++#9

とにかくとにかくstd::atomic std::atomic に特殊メモリを扱う機能はないに特殊メモリを扱う機能はない

↓↓atomic atomic でも特殊メモリ使いたいならでも特殊メモリ使いたいなら

volatile std::atomic< int > vai;

とと volatile volatile とと atomic atomic を併⽤用するを併⽤用する

Page 35: Effective modern-c++#9

参考: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 にに 特殊メモリを扱うことが可能特殊メモリを扱うことが可能

Page 36: Effective modern-c++#9

Things to RememberThings to Remember

std::atomic std::atomic はは mutex mutex を⽤用いないで複数スレッドかを⽤用いないで複数スレッドからアクセスできるデータを表現するらアクセスできるデータを表現する

volatile volatile はは, (, (読み取り読み取り//書き込みを最適化すべきはな書き込みを最適化すべきはないい) ) 特殊なメモリを表現する特殊なメモリを表現する(compiler (compiler に教えるに教える))