Преобразование из вектора C++ в Numpy ndarray происходит очень медленно

Я использую Boost python для вычислительно интенсивных частей программы, и он работает довольно хорошо, за исключением того, что передача массивов из C++ в python и наоборот очень медленная, до такой степени, что это ограничивающий фактор для общей эффективности программы. .

Вот пример, чтобы проиллюстрировать мою точку зрения. На стороне C++ я возвращаю матрицу с типом vector< vector<double> > относительно большого размера. На стороне Python я вызываю эту функцию и пытаюсь преобразовать полученный массив двумя разными методами: методом numpy.array и моей собственной (вероятно, довольно наивной) реализацией базового преобразователя на C++. Часть C++:

#include <boost/python.hpp>
#include <boost/python/numpy.hpp>
#include <boost/python/suite/indexing/vector_indexing_suite.hpp>

using namespace std;

typedef vector<double> vec;
typedef vector<vec> mat;

mat test()
{
    int n = 1e4;
    mat result(n, vec(n, 0.));
    return result;
}

namespace p = boost::python;
namespace np = boost::python::numpy;

np::ndarray convert_to_numpy(mat const & input)
{
    u_int n_rows = input.size();
    u_int n_cols = input[0].size();
    p::tuple shape = p::make_tuple(n_rows, n_cols);
    np::dtype dtype = np::dtype::get_builtin<double>();
    np::ndarray converted = np::zeros(shape, dtype);

    for (u_int i = 0; i < n_rows; i++)
    {
        for (u_int j = 0; j < n_cols; j++)
        {
            converted[i][j] = input[i][j];
        }
    }
    return converted;
}


BOOST_PYTHON_MODULE(hermite_cpp)
{
    using namespace boost::python;

    // Initialize numpy
    Py_Initialize();
    boost::python::numpy::initialize();

    class_<vec>("double_vec")
        .def(vector_indexing_suite<vec>())
        ;

    class_<mat>("double_mat")
        .def(vector_indexing_suite<mat>())
        ;

    def("convert_to_numpy", convert_to_numpy);
    def("test", test);
}

Часть питона:

import test
import numpy as np
import time


def timeit(function):
    def wrapper(*args, **kwargs):
        tb = time.time()
        result = function(*args, **kwargs)
        te = time.time()
        print(te - tb)
        return result
    return wrapper


A = timeit(test.test)()
B = timeit(np.array)(A)
C = timeit(test.convert_to_numpy)(A)

Результаты этой программы следующие:

0.56
36.68
26.56

Можно ли сделать преобразование быстрее? Или, что еще лучше, можно было бы использовать массив между numpy и C++. Я долго гуглил, но без особого успеха.

Почему в Python есть оператор "pass"?
Почему в Python есть оператор "pass"?
Оператор pass в Python - это простая концепция, которую могут быстро освоить даже новички без опыта программирования.
Некоторые методы, о которых вы не знали, что они существуют в Python
Некоторые методы, о которых вы не знали, что они существуют в Python
Python - самый известный и самый простой в изучении язык в наши дни. Имея широкий спектр применения в области машинного обучения, Data Science,...
Основы Python Часть I
Основы Python Часть I
Вы когда-нибудь задумывались, почему в программах на Python вы видите приведенный ниже код?
LeetCode - 1579. Удаление максимального числа ребер для сохранения полной проходимости графа
LeetCode - 1579. Удаление максимального числа ребер для сохранения полной проходимости графа
Алиса и Боб имеют неориентированный граф из n узлов и трех типов ребер:
Оптимизация кода с помощью тернарного оператора Python
Оптимизация кода с помощью тернарного оператора Python
И последнее, что мы хотели бы показать вам, прежде чем двигаться дальше, это
Советы по эффективной веб-разработке с помощью Python
Советы по эффективной веб-разработке с помощью Python
Как веб-разработчик, Python может стать мощным инструментом для создания эффективных и масштабируемых веб-приложений.
1
0
1 553
3
Перейти к ответу Данный вопрос помечен как решенный

Ответы 3

Это только частичный ответ, потому что я не совсем понимаю, почему он работает, но я обнаружил, что переписав функцию преобразования как

np::ndarray convert_to_numpy(mat const & input)
{
    u_int n_rows = input.size();
    u_int n_cols = input[0].size();
    p::tuple shape = p::make_tuple(n_rows, n_cols);
    p::tuple stride = p::make_tuple(sizeof(double));
    np::dtype dtype = np::dtype::get_builtin<double>();
    p::object own;
    np::ndarray converted = np::zeros(shape, dtype);

    for (u_int i = 0; i < n_rows; i++)
    {
        shape = p::make_tuple(n_cols);
        converted[i] = np::from_data(input[i].data(), dtype, shape, stride, own);
    }
    return converted;
}

значительно ускоряет работу.

Другое решение - использовать Boost::Multi_array, чтобы обеспечить непрерывное хранение матрицы в памяти, что дает еще более быстрые результаты.

typedef boost::multi_array<double, 2> c_mat;
np::ndarray convert_to_numpy(c_mat const & input)
{
    u_int n_rows = input.shape()[0];
    u_int n_cols = input.shape()[1];
    p::tuple shape = p::make_tuple(n_rows, n_cols);
    p::tuple strides = p::make_tuple(input.strides()[0]*sizeof(double),
                                     input.strides()[1]*sizeof(double));
    np::dtype dtype = np::dtype::get_builtin<double>();
    p::object own;
    np::ndarray converted = np::from_data(input.data(), dtype, shape, strides, own);
    return converted;
}
Ответ принят как подходящий

Я делал эти преобразования таким образом, и они выполнялись довольно быстро:

void convert_to_numpy(const mat & input, p::object obj)
{
    PyObject* pobj = obj.ptr();
    Py_buffer pybuf;
    PyObject_GetBuffer(pobj, &pybuf, PyBUF_SIMPLE);
    void *buf = pybuf.buf;
    double *p = (double*)buf;
    Py_XDECREF(pobj);

    u_int n_rows = input.size();
    u_int n_cols = input[0].size();
    for (u_int i = 0; i < n_rows; i++)
    {
        for (u_int j = 0; j < n_cols; j++)
        {
            p[i*n_cols+j] = input[i][j];
        }
    }
}

Затем в питоне:

C = np.empty([10000*10000], dtype=np.float64)
timeit(test.convert_to_numpy)(A,C)

Сроки:

0.557882070541
0.12882900238

Спасибо за ваш вклад! Вы знаете, как это можно адаптировать к двумерным аргументам?

Rastapopoulos 03.05.2018 20:43

Используйте numpy.reshape.

doqtor 03.05.2018 21:12

Я использую прямой вызов from_data с использованием vector.data () в качестве источника

vector<double>vertices;
auto np_verts= np::from_data(vertices.data(),     // data ->
            np::dtype::get_builtin<double>(),  // dtype -> double
            p::make_tuple(vertices.size()),    // shape -> size
            p::make_tuple(sizeof(double)), p::object());    // stride 1

Другие вопросы по теме