pybind11ではintやdoubleといった基本型だけでなく、
自作のclassも引数や返り値で使えるようだ。
intはstd::vectorとかstd::valarrayで渡すことができた。
classもstd::vectorとすれば渡せるようだ。
C++のマルチスレッドサンプル
まずはC++のコードから。
main関数があるので普通のexeプログラムだ。
CTestクラスは1つ当たり3個のdouble変数を持ち、
main()で3600万回ループを回しているので、
およそ1億個の乱数を生成していることになる。
手元のマシンでは3200ミリ秒ほど。
タスクマネージャーで見ても全てのコアが使われている。
main.cpp
#include <pybind11/pybind11.h>
#include <pybind11/stl.h>
namespace py = pybind11;
#include <array>
#include <string>
#include <chrono>
#include <iostream>
#include <random>
#include <thread>
#include <future>
class CTest {
public:
std::array<double, 3> data;
void set(const std::array<double, 3> &x) {
data = x;
}
CTest() {};
~CTest() {};
}
// 乱数生成
// output 確保済み領域のポインタ
// number 発生させる乱数の個数
// rand_min, rand_max 乱数の範囲
void MT_RAND_vec(double* output, const int &number, const double &rand_min, const double &rand_max) {
std::random_device rd;
std::mt19937 mt(rd());
std::uniform_real_distribution<double> MT_RAND(rand_min, rand_max);
for (auto i = 0; i < number; ++i) {
output[i] = MT_RAND(mt);
}
}
// 汎用マルチスレッドルーチン
void MT(void(*func)(std::vector<CTest>*, int, int), std::vector<CTest>* input, const int length) {
const int num_thread = 12; // 最大スレッド数
const int unit = length / num_thread; // 1スレッド当たりの処理量
int step = 0;
std::vector<std::thread> t(num_thread); // スレッドオブジェクト生成
for (auto i = 0; i < num_thread; ++i) { // スレッドを順次生成
t[i] = std::thread(func, input, step, unit);
step += unit;
}
// 全てのスレッドが終了するまで待機
for (auto& threads : t) {
threads.join();
}
}
// 子スレッドにする関数
// startの位置からnumber個の乱数を生成して書き込む
void TH_set_RAND(std::vector<CTest>* input, const int start, const int number) {
double *temp = new double[number * 3];
MT_RAND_vec(temp, number * 3, 0.0, 256.0);
for (auto i = 0; i < number; ++i) {
(*input)[start + i].set({ temp[3 * i] , temp[3 * i + 1] , temp[3 * i + 2] });
}
delete[]temp;
}
// <vector>を与えるとマルチスレッドルーチンを起動させる
std::vector<CTest> vecMT_set_RAND(std::vector<CTest> input) {
int length = static_cast<int>(input.size());
std::vector<CTest>* ptr = &input;
MT(TH_set_RAND, ptr, length);
return input;
}
int main() {
std::chrono::system_clock::time_point start, end; // 型は auto で可
start = std::chrono::system_clock::now(); // 計測開始時間
int loop_number = 36000000;
std::vector<CTest>X(loop_number);
vecMT_set_RAND(X);
end = std::chrono::system_clock::now(); // 計測終了時間
double elapsed = static_cast<double>(std::chrono::duration_cast<std::chrono::milliseconds>(end - start).count()); //処理に要した時間をミリ秒に変換
std::cout << "処理時間は" << elapsed << "ミリ秒です。" << '\n';
return nRetCode;
}
Pythonから呼んでみる
多少メソッドを追加しつつ……
dllのプロジェクトにコードをコピーして書いていく。
dll.cpp
PYBIND11_PLUGIN(CTestLib) {
py::module m("CTestLib", "pybind11 example plugin");
py::class_<CTest>(m, "CTest")
.def(py::init())
.def("set", &CTest::set)
.def("set_RAND", &CTest::set_RAND);
m.def("vec_create", &vec_create, "C++");
m.def("vecMT_set_RAND", &vecMT_set_RAND, "C++");
return m.ptr();
}
dllTest.py
import CTestLib
Temp_vector = CTestLib.vec_create(1200000)
Rand_vector = CTestLib.vecMT_set_RAND(Temp_vector)
print (Rand_vector[1199999].data)
これで動くんだ。
ちゃんと120万個のクラス配列に乱数が3つずつ格納されてる。
ただしこれで同じく3000ミリ秒ぐらい。
ざっと30倍差はついてる。
タスクマネージャーを見ると1コア分しか使ってないようで、
それを加味しても3倍以上の速度差がある。
これはC++のクラス配列をPythonのクラス配列に変換するときのオーバーヘッドなのだろうか。
どっちにしろ普通にマルチスレッドにしても意味がないな。