SHOEISHA iD

※旧SEメンバーシップ会員の方は、同じ登録情報(メールアドレス&パスワード)でログインいただけます

DeveloperZine(デベロッパージン)- エンジニアの意思決定を支える技術情報メディア ProductZine

CodeZine編集部では、現場で活躍するデベロッパーをスターにするためのカンファレンス「Developers Summit」や、エンジニアの生きざまをブーストするためのイベント「Developers Boost」など、さまざまなカンファレンスを企画・運営しています。

特集記事

Visual Studio 2017 updateでサポートされた「C++17のparallel algorithms」

reduceとscan

 parallel algorithmの導入に併せて数値アルゴリズム<numeric>にreduceとscanが追加されています。

reduce:並列版accumulate
reduce(policy, first, last, init, binary_op)

 binary_opは二項演算(引数2つの関数オブジェクト)、これを◎で表すと:


init ◎*first ◎*(first+1)◎... ◎*(last-1)

を求めます。このとき二項演算が結合則:(x ◎y) ◎z = x ◎(y ◎z) および交換則:x ◎y = y ◎xを満足するなら、上式はどこからどんな順序で計算しても同じ結果が得られます。この性質を利用して並列化しようって魂胆です。a ◎b ◎c ◎dに対しa ◎b とc ◎dを並列に(同時に)計算しても構わんのですから。

list05 reduce.cpp
array<int,1000> data;
iota(data.begin(), data.end(), 1); // 1, 2, 3... で埋める
// 1 + 2 +...+ 1000 を求める
int sum = reduce(execution::par, data.begin(), data.end(), 0, plus<int>());
cout << sum << endl; // 500500
transform_reduce:並列版inner_product
tranform_reduce(policy, first, last, init, binary_op, unary_op)

 二項演算:binary_opを◎、単項演算:unary_opをf()で表すと:

init ◎f(*first) ◎f(*(first+1))◎... ◎f(*(last-1))

を求めます。

list-06 transform_reduce.cpp
array<int,7> data = { 1, 2, 3, 4, 5, 6, 7 };
// 1 + 3 + 5 + ... は平方数
int sum = transform_reduce(execution::par, data.begin(), data.end(), 0, plus<int>(), [](int n) { return 2*n-1; });
cout << sum << endl; // 49 (7^2)

 入力シーケンスを2つ与えるタイプもあって

tranform_reduce(policy, first1, last1, first2, init, binary_op1, binary_op2)

 二項演算:binary_op1, binary_op2をそれぞれ◎とf()で表すと:

init ◎f(*first1,*first2) ◎f(*(first1+1),*(first2+1)◎...

を求めます。binary_op1, binary_op2が加算,乗算なら、2つのベクトルの内積(inner_product)を求めることになります。

inclusive_scan:並列版partial_sum
inclusive_scan(policy, first, last, out, binary_op, init)

 *outにはinit ◎*first、*(out+1)にはinit ◎*first ◎*(first+1)、……、*(out+i)にはfirstからfirst+iまでのreduce()の結果が求まります。

list-07 inclusive_scan.cpp
array<int,7> data = { 1, 3, 5, 7, 9, 11, 13 };
array<int,7> out;
// 1 + 3 + 5 + ... は平方数
inclusive_scan(execution::par, data.begin(), data.end(), out.begin(), plus<int>(), 0);
for ( int sum : out ) {
  cout << sum << ' '; // 1 4 9 16 25 36 49
}

 reduce()と同様、入力シーケンスを2つ与えるタイプも定義されています。

exclusive_scan

 ふるまいはinclusive_scan()とほとんど同じ、異なるのは*(out+i)にはfirstからfirst+iの"直前"までのreduce()の結果が得られること。i番目の結果にi番目の入力が含まれないので"exclusive"なのです。

 あとtransform_inclusive_scan, transform_exclusive_scanがあるけど……説明割愛!

 繰り返しになりますが、reduce/scanは与える二項演算が結合則と交換則を満たさないと、その動作は非決定的(non-deterministic)、つまり結果が一意に定まらないのでご注意を。

 ……待ってたんですよparallel algorithms、こんなにお手軽な並列化が標準C++でサポートされるんだから、三途の川の向こう岸から還ってきた甲斐がありました。thread poolやconcurrent containerの類が標準化されるとステキなんですけど……もうしばらく、生きてみましょかね♪

この記事は参考になりましたか?

連載通知を行うには会員登録(無料)が必要です。
既に会員の方はを行ってください。
特集記事連載記事一覧

もっと読む

この記事の著者

επιστημη(エピステーメー)

C++に首まで浸かったプログラマ。Microsoft MVP, Visual C++ (2004.01~2018.06) "だった"りわんくま同盟でたまにセッションスピーカやったり中国茶淹れてにわか茶...

※プロフィールは、執筆時点、または直近の記事の寄稿時点での内容です

この記事は参考になりましたか?

この記事をシェア

CodeZine(コードジン)
https://codezine.jp/article/detail/10890 2018/07/06 14:00

イベント

CodeZine編集部では、現場で活躍するデベロッパーをスターにするためのカンファレンス「Developers Summit」や、エンジニアの生きざまをブーストするためのイベント「Developers Boost」など、さまざまなカンファレンスを企画・運営しています。

新規会員登録無料のご案内

  • ・全ての過去記事が閲覧できます
  • ・会員限定メルマガを受信できます

メールバックナンバー