Effective Modern C++#9Effective Modern C++#9
2015/09/30
@simizut22
item40item40
並⾏行処理には並⾏行処理には atomicatomicをを
特殊メモリには特殊メモリには volatilevolatile をを
volatilevolatile が並⾏行プログラムが並⾏行プログラム
で役に⽴立たない理由で役に⽴立たない理由11
volatile int vc{0}; // volatile counter
std::atomic< int > ac{0}; // atomic counter
void f() {
++vc; // invrement vc
++ac; // atomically increment
}
single-threadsingle-thread でで((1度だけ1度だけ)) 呼ぶなら,呼ぶなら,vc, acvc, ac とと
もにもに 11 だがだが......
n-threadn-thread から呼び出したらどうなるから呼び出したらどうなる????
変数 値
ac 2
vc ????
注意: バグらせるために若干コードを修正しています
http://melpon.org/wandbox/permlink/a8USjwKXZ3uiVXpC
thread 1 thread 2
1 vc の読み取り
2 vc の読み取り
3 vc に書き込み
4 vc に書き込み
例えば例えば......
と実⾏行された場合はと実⾏行された場合は 11 になる.になる.((実際には実際には UB)UB)
アトミック操作はスレッド間でデータをやり取りするアトミック操作はスレッド間でデータをやり取りする
ための最も基本的な同期プリミティブであり、ための最も基本的な同期プリミティブであり、
変数への不可分変数への不可分(atomic)(atomic)な読み込みな読み込み//書き込み書き込み//読み書き読み書き
を同時に⾏行う操作を同時に⾏行う操作(Read-Modify-Write operation)(Read-Modify-Write operation)
を提供する.を提供する. [[引⽤用引⽤用]]cpprefjp:atomiccpprefjp:atomic
atomicatomic
isoiso が定める範囲のが定める範囲の volatilevolatile にはこのような機能は定にはこのような機能は定
義されていない義されていない
* ただし, implementation-defined なので,独⾃自拡張も存在
例: msvc
https://msdn.microsoft.com/en-us/library/12a04hfd.aspx
補⾜足:補⾜足:CC標準の標準の
volatilevolatile
volatile:volatile: 処理系が制御できない⽅方法で変数が変更され処理系が制御できない⽅方法で変数が変更され
うることを処理系に伝えるうることを処理系に伝える
副作⽤用完了点を跨いでの最適化に関与すべきでない副作⽤用完了点を跨いでの最適化に関与すべきでない
** ⼆二つの副作⽤用完了点の間で⾏行うのは問題ない⼆二つの副作⽤用完了点の間で⾏行うのは問題ない
1. atomic1. atomic 性がないから性がないから
volatilevolatile が並⾏行プログラムが並⾏行プログラム
で役に⽴立たない理由で役に⽴立たない理由22
std::atomicstd::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::atomicstd::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
volatilevolatile にはそのような効にはそのような効
果はない!!果はない!!
(iso(iso で定める範囲ではで定める範囲では))
(*)msvc だと
1. volatile object への書き込みは release semantics を持つ
2. volatile object への書き込みは acquire semantics を持つ
らしい(ただし ARM でなく, /volatile:iso 指定がされていない時)
volatilevolatile が並⾏行プログラムで役に⽴立が並⾏行プログラムで役に⽴立
たない理由たない理由((まとめまとめ))
1. atomic1. atomic 性がない性がない
2.2. コードの並び替えの抑制をしないコードの並び替えの抑制をしない
いついつ volatilevolatile が有⽤用かが有⽤用か????
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 してみる
👆簡約されていないことがわかる
特殊なメモリを扱うときに特殊なメモリを扱うときに volatilevolatile が役に⽴立つが役に⽴立つ
(( というかそれ以外の意味が本来はない。。。というかそれ以外の意味が本来はない。。。))
atomicatomic はは volatilevolatile の代わりになるかの代わりになるか????
xx をを volatile intvolatile 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 ののatomicatomic なな copycopy は怪しいのでそこを省いて、は怪しいのでそこを省いて、 yy なしにしてもならなかったなしにしてもならなかった
とにかくとにかく
std::atomicstd::atomic に特殊メモリを扱う機能はないに特殊メモリを扱う機能はない
↓↓
atomicatomic でも特殊メモリ使いたいならでも特殊メモリ使いたいなら
volatile std::atomic< int > vai;
とと volatilevolatile とと atomicatomic を併⽤用するを併⽤用する
参考:yohhoyの⽇日記~volatile版atomic操作関数が存在する理由~
例えば、例えば、 fetch_addfetch_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;
}
となっており、となっており、
atomicatomic にに 特殊メモリを扱うことが可能特殊メモリを扱うことが可能
Things to RememberThings to Remember
std::atomicstd::atomic はは mutexmutex を⽤用いないで複数スレッドかを⽤用いないで複数スレッドか
らアクセスできるデータを表現するらアクセスできるデータを表現する
volatilevolatile はは, (, (読み取り読み取り//書き込みを最適化すべきはな書き込みを最適化すべきはな
いい)) 特殊なメモリを表現する特殊なメモリを表現する(compiler(compiler に教えるに教える))

Effective modern-c++#9