windows10の環境でC++のマルチスレッド・べクトル化プログラムを実行してみます。
cpuがintel core i7-3700 (4core/8thread)の環境です。
実行ポリシーを指定できる <execution>
*1
が gcc9(C++17) から使えるそうなので
*2
mingw-w64のgccのインストールからやりましたので本記事でまとめていきます。
目次
MSYS2,mingw-w64-gcc,gdbのインストール
MSYS2 homepageに書いてあることに従ってMSYS2のインストールとパッケージのアップデートをします。
次にMSYS2の端末上でmingw-w64のgccとgdbをインストールします。
pacman -S mingw-w64-x86_64-toolchain
と入力すると選択肢が現れますので、3と9を選びました。
これでインストールが完了して、インストール先のパスを通せば端末上からgccが使えるようになりました。
g++ --version g++.exe (Rev2, Built by MSYS2 project) 9.2.0 Copyright (C) 2019 Free Software Foundation, Inc. This is free software; see the source for copying conditions. There is NO warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
これはpowershellでの実行です。
Intel TBB のインストール
<execution>
はIntel TBB(Threading Building Blocks)を使っているそうなのでこれもインストールします。
*3
pacman -S mingw-w64-x86_64-intel-tbb
をMSYS2上で実行しました。
実行例
実行環境が整いましたので実際にプログラムを実行してみます。
ここではvector<int64_t>
の要素の総和をマルチスレッド化・ベクトル化して計算してみます。
以下の6つのパターンの実行時間の比較をします。マルチスレッド化は私の環境では最大で8スレッド使えます。
accumulate
のシングルスレッド実行future
による、分割区間のaccumulate
マルチスレッド実行reduce
のシングルスレッド実行reduce
のシングルスレッド+ベクトル化実行reduce
のマルチスレッド実行reduce
のマルチスレッド+ベクトル化実行
以下のプログラムを実行してみました。
// accumulate.cpp #include <iostream> #include <future> #include <vector> #include <cmath> #include <chrono> #include <numeric> #include <iterator> #include <execution> using namespace std; const int64_t len = 100'000'000; // ベクトルサイズ const int Nth = 8; //スレッド数 const int64_t I = len / Nth; // スレッド計算区間距離 // accumulate の分割非同期実行 template<class InputIt, class T> T accum_multi(InputIt first, InputIt last, T init){ vector<future<T>> f; for(int i = 0; i < Nth-1; ++i){ //最後より前の区間を他スレッドへ f.push_back(async(launch::async, accumulate<InputIt, T>, first + I * i, first + I * (i + 1), (T)0)); } T sum = accumulate(first + I * (Nth-1), last, init); //最後の区間はこのスレッド for(future<T> &F : f){ // 他スレッドの結果を足す. F.wait(); sum += F.get(); } return sum; } int main(){ vector<int64_t> A(len); iota(A.begin(), A.end(), 1); // A = {1,2,3,4,...,len} int64_t sum; //1. accumulate chrono::system_clock::time_point time = chrono::system_clock::now(); sum = accumulate(A.begin(), A.end(), 0LL); int t = chrono::duration_cast<chrono::milliseconds>(chrono::system_clock::now() - time).count(); cout << "1. "<< sum << " : " << t << " msec\n"; //2. future実装マルチスレッドaccumulate time = chrono::system_clock::now(); sum = accum_multi(A.begin(), A.end(), 0LL); t = chrono::duration_cast<chrono::milliseconds>(chrono::system_clock::now() - time).count(); cout << "2. "<< sum << " : " << t << " msec\n"; //3. reduce シングルスレッド time = chrono::system_clock::now(); sum = reduce(execution::seq, A.begin(), A.end()); t = chrono::duration_cast<chrono::milliseconds>(chrono::system_clock::now() - time).count(); cout << "3. "<< sum << " : " << t << " msec\n"; //4. reduce シングルスレッド+ベクトル化 time = chrono::system_clock::now(); sum = reduce(execution::unseq, A.begin(), A.end()); t = chrono::duration_cast<chrono::milliseconds>(chrono::system_clock::now() - time).count(); cout << "4. "<< sum << " : " << t << " msec\n"; //5. reduce マルチスレッド time = chrono::system_clock::now(); sum = reduce(execution::par, A.begin(), A.end()); t = chrono::duration_cast<chrono::milliseconds>(chrono::system_clock::now() - time).count(); cout << "5. "<< sum << " : " << t << " msec\n"; //6. reduce マルチスレッド+ベクトル化 time = chrono::system_clock::now(); sum = reduce(execution::par_unseq, A.begin(), A.end()); t = chrono::duration_cast<chrono::milliseconds>(chrono::system_clock::now() - time).count(); cout << "6. "<< sum << " : " << t << " msec\n"; }
コンパルの際にはIntel TBBを使うために -ltbb
を最後に書きます。
g++ -g -std=c++1z .\accumulate.cpp -ltbb .\a.exe 1. 5000000050000000 : 683 msec 2. 5000000050000000 : 201 msec 3. 5000000050000000 : 1087 msec 4. 5000000050000000 : 678 msec 5. 5000000050000000 : 268 msec 6. 5000000050000000 : 164 msec
実行結果を速度が早い順に並べると。
6. reduce
のマルチスレッド+ベクトル化実行
2. future
による、分割区間の accumulate
マルチスレッド実行
5. reduce
のマルチスレッド実行
4. reduce
のシングルスレッド+ベクトル化実行
1. accumulate
のシングルスレッド実行
3. reduce
のシングルスレッド実行
のようになりました。
2番目はわざわざ自分で実装した関数なのですが、それよりもSTL関数の6番目が速いというのは知れてよかったです。
gcc9以上の環境では実行ポリシーを指定できる関数を積極的に使っていきたいですね。(一応補足ですが、2番目は<future>
しか使ってないので、gcc4.3(C++11)くらいから動きます。)