Проблема параллельной обработки Pybind11 в параллелизме :: parallel_for - PullRequest
0 голосов
/ 10 марта 2020

У меня есть python код, который выполняет фильтрацию на матрице. Я создал интерфейс C ++ с использованием pybind11, который успешно работает в сериализованном виде (см. Код ниже).

Я пытаюсь сделать это параллельной обработкой, чтобы, надеюсь, сократить время вычислений по сравнению с его сериализованной версией. Для этого я разделил свой массив размером M×N на три подматрицы размером M×(N/3), чтобы обрабатывать их параллельно, используя один и тот же интерфейс.

Я использовал библиотеку ppl.h, чтобы создать параллель для -l oop, и в каждом l oop вызывал функцию python для подматрицы размера M×(N/3).

#include <iostream>
#include <ppl.h>

#include "pybind11/embed.h"
#include <pybind11/iostream.h>
#include <pybind11/stl_bind.h>
#include "pybind11/eigen.h"
#include "pybind11/stl.h"
#include "pybind11/numpy.h"
#include "pybind11/functional.h"
#include <Eigen/Dense>

namespace py = pybind11;

class myClass
{
public:
    myClass()
    {
        m_module = py::module::import("myFilterScript");
        m_handle = m_module.attr("medianFilter");
    };

    void medianFilterSerialized(Eigen::Ref<Eigen::MatrixXf> input, int windowSize) 
    {
        Eigen::MatrixXf output;
        output.resizeLike(input);
        output = m_handle(input, windowSize).cast<Eigen::MatrixXf>();
    };

    void medianFilterParallelizedUsingPPL(Eigen::Ref<Eigen::MatrixXf> input, int windowSize) 
    {
        Eigen::MatrixXf output;
        output.resizeLike(input);
        /* Acquire GIL before calling Python code */
        //py::gil_scoped_acquire acquire;
        Concurrency::parallel_for(size_t(0), size_t(3), [&](size_t i)
        {
            output.block(0, i * input.cols() / 3, input.rows(), input.cols() / 3) = m_handle(input.block(0, i * input.cols() / 3, input.rows(), input.cols() / 3).array(), windowSize).cast<Eigen::MatrixXf>();
        });
        //py::gil_scoped_release release;
    };

private:
    py::scoped_interpreter m_guard;
    py::module m_module;
    py::handle m_handle;
    py::object m_object;
};


int main()
{
    myClass c;

    Eigen::MatrixXf input = Eigen::MatrixXf::Random(240, 120);

    c.medianFilterSerialized(input, 3); 
    c.medianFilterParallelizedUsingPPL(input, 3);

    return 0;
}

myFilterScript.py:

import threading
import numpy as np
import bottleneck as bn # can be installed from https://pypi.org/project/Bottleneck/

def medianFilter(input, windowSize):
    return bn.move_median(input, window=windowSize, axis=0)

Независимо от использования py::gil_scoped_acquire мой код вылетает при достижении for-l oop:

Access violation reading location // or:
Unhandled exception at 0x00007FF98BB8DB8E (ucrtbase.dll) in Pybind11_Parallelizing.exe: Fatal program exit requested.

Может ли кто-нибудь помочь мне понять, можно ли параллельно вызывать загруженную функцию модуля python в многопроцессорном или многопоточном режиме? Чего мне не хватает в моем коде? Пожалуйста, дайте мне знать. Заранее спасибо.

1 Ответ

1 голос
/ 10 марта 2020

py::gil_scoped_acquire - это объект RAII для получения GIL в области, аналогично py::gil_scoped_release в «обратном» RAII для освобождения GIL в области. Таким образом, в соответствующей области вам нужен только первый.

Область для получения GIL находится на функции, которая вызывает Python, то есть внутри лямбды, которую вы передаете parallel_for: каждый поток для выполнения необходимо сохранить GIL для доступа к любым Python объектам или API, в данном случае m_handle. Однако в лямбда-выражениях полностью сериализуется код, что делает использование потоков спорным, так что это решит вашу проблему по неправильным причинам.

Это может быть в случае использования суб-интерпретаторов, для которых существует нет прямой поддержки в pybind11 (https://pybind11.readthedocs.io/en/stable/advanced/embedding.html#sub -interpreter-support ), поэтому API C будет билетом (https://docs.python.org/3/c-api/init.html#c .Py_NewInterpreter ). Дело в том, что используемые данные не Python и все операции в принципе независимы.

Однако вам необходимо знать, является ли Bottleneck поточно-ориентированным. Из поверхностного взгляда видно, что он не имеет глобальных / stati c данных AFAICT. Теоретически, тогда есть место для распараллеливания: вам нужно держать GIL при вызове move_median, когда он вводит код Cython, используемый для связывания Bottleneck (он распаковывает переменные, вызывая, таким образом, Python API), затем Cython может высвобождать GIL при вводе кода C Bottleneck и повторно запрашивать при выходе, после чего происходит освобождение в лямбде, когда заканчивается область RAII. Затем код C выполняется параллельно.

Но возникает вопрос: почему вы вызываете библиотеку C из C ++ через ее привязки Python в первую очередь? Здесь выглядит тривиальное решение: пропустите Python и вызовите функцию move_median C напрямую.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...