LoginSignup
0
1

More than 5 years have passed since last update.

pybind11でclassを<vector>にしてC++側でマルチスレッド処理する

Last updated at Posted at 2017-02-21

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のクラス配列に変換するときのオーバーヘッドなのだろうか。
どっちにしろ普通にマルチスレッドにしても意味がないな。

0
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
1