私のプロジェクトでは、pybind11を使用してC++コードをPythonにバインドします。最近、私は非常に大きなデータセット(70GB +)に対処しなければならず、std::deque
のデータを複数に分割する必要が生じましたstd::deque
。私のデータセットは非常に大きいので、私はスプリットがメモリオーバーヘッドの多くを持っていないと思っています。だから私は一般的に私の要件が満たされることを確実にしなければならない1つのプッシュ戦略を行った。C++はデキューからデキューへ要素を移動するときに2倍のメモリを使用します
これはすべて理論です。実際には、私のプロセスが殺されました。だから私は過去2日間苦労し、最終的に問題を示す最小限の例を考え出しました。
一般的に、最小限の例では、deque
(〜11GB)にデータの束を作成し、それをPythonに返してから、C++
を呼び出して要素を移動します。そのような単純な。移動部分はエグゼキュータで実行されます。
私はエグゼキュータを使用しないと、メモリ使用量が予想どおりであり、ulimitによる仮想メモリの制限が課せられても、プログラムは実際にこれらの制限を守り、クラッシュしません。
test.py
from test import _test
import asyncio
import concurrent
async def test_main(loop, executor):
numbers = _test.generate()
# moved_numbers = _test.move(numbers) # This works!
moved_numbers = await loop.run_in_executor(executor, _test.move, numbers) # This doesn't!
if __name__ == '__main__':
loop = asyncio.get_event_loop()
executor = concurrent.futures.ThreadPoolExecutor(1)
task = loop.create_task(test_main(loop, executor))
loop.run_until_complete(task)
executor.shutdown()
loop.close()
TEST.CPP
#include <deque>
#include <iostream>
#include <pybind11/pybind11.h>
#include <pybind11/stl.h>
namespace py = pybind11;
PYBIND11_MAKE_OPAQUE(std::deque<uint64_t>);
PYBIND11_DECLARE_HOLDER_TYPE(T, std::shared_ptr<T>);
template<class T>
void py_bind_opaque_deque(py::module& m, const char* type_name) {
py::class_<std::deque<T>, std::shared_ptr<std::deque<T>>>(m, type_name)
.def(py::init<>())
.def(py::init<size_t, T>());
}
PYBIND11_PLUGIN(_test) {
namespace py = pybind11;
pybind11::module m("_test");
py_bind_opaque_deque<uint64_t>(m, "NumbersDequeue");
// Generate ~11Gb of data.
m.def("generate", []() {
std::deque<uint64_t> numbers;
for (uint64_t i = 0; i < 1500 * 1000000; ++i) {
numbers.push_back(i);
}
return numbers;
});
// Move data from one dequeue to another.
m.def("move", [](std::deque<uint64_t>& numbers) {
std::deque<uint64_t> numbers_moved;
while (!numbers.empty()) {
numbers_moved.push_back(std::move(numbers.back()));
numbers.pop_back();
}
std::cout << "Done!\n";
return numbers_moved;
});
return m.ptr();
}
テスト/ __ init__.py
import warnings
warnings.simplefilter("default")
コンパイル:
g++ -std=c++14 -O2 -march=native -fPIC -Iextern/pybind11 `python3.5-config --includes` `python3.5-config --ldflags` `python3.5-config --libs` -shared -o test/_test.so test.cpp
観察:
- 可動部がエグゼキュータによって行われていないので、我々はちょうど
moved_numbers = _test.move(numbers)
を呼び出して、すべての作品予想通り、メモリ使用量がhtopのであったが周り11Gb
のまま、 すばらしいです!。 - 実行部分が実行プログラムで実行されると、プログラムはメモリを2倍にしてクラッシュします。
仮想メモリの制限が導入されると(〜15Gb)、すべてがうまく動作します。これはおそらく最も興味深い部分です。
ulimit -Sv 15000000 && python3.5 test.py
>>Done!
です。制限を大きくすると、プログラムがクラッシュします(150Gb> my RAM)。
ulimit -Sv 150000000 && python3.5 test.py
>>(すべきであり、またそれ)助けないshrink_to_fit
両端キュー方式の[1] 2573 killed python3.5 test.py
使用
使用するソフトウェア
Ubuntu 14.04
gcc version 5.4.1 20160904 (Ubuntu 5.4.1-2ubuntu1~14.04)
Python 3.5.2
pybind11 latest release - v1.8.1
注
この例は、単に問題を示すために作成されたものです。問題が発生するには、asyncio
とpybind
の使用が必要です。
何が起こっているかについてのアイデアは、大歓迎です。