Снижение производительности после внедрения Python - PullRequest
0 голосов
/ 24 июня 2019

Вопрос: я пытаюсь вызвать функцию Python / Numpy из C ++ (Embedded Python).Но задержка этой функции примерно в 3 раза ниже при вызове из C ++ по сравнению с производительностью, наблюдаемой на IPython.

Ожидается ли это, или я делаю это неправильно?


Python

Ниже мой код Numpy для встраивания:

# calc.py
import numpy as np
def process(data, S, M):
    return (data-S)*M

С IPython% timeit я получаю около 3,5 мс

import numpy as np
from calc import preprocess
H = 1024
A = np.ones((1, 3, H, H), dtype=np.float32) * 100
B = np.ones((1, 3, 1, 1), dtype=np.float32) * 50
C = np.ones((1, 3, 1, 1), dtype=np.float32) * 0.5
Z = preprocess(A, B, C)

In [8]: %timeit Z = preprocess(A, B, C)
100 loops, best of 3: 3.46 ms per loop

C ++

Вот полный код C ++, который я использовал для тестирования той же функции: profile.cpp .Для встраивания используется библиотека Pybind11 .Это библиотека только для заголовков, так что вы можете просто клонировать репо и включить заголовок, чтобы он заработал.

#include <pybind11/embed.h>
#include <pybind11/numpy.h>
#include <iostream>
#include <numpy/arrayobject.h>
#include <chrono>
#include <vector>
namespace py = pybind11;


// Convert C++ Vector to py::array_t
template<typename T>
py::array_t<T> cppvector2pyarray(std::vector<T>& vec, const std::vector<int>& shape) {
    std::vector<ssize_t> stride(shape.size(), sizeof(T));
    for(int i=shape.size()-2; i >= 0; --i) {
        stride[i] = stride[i+1] * shape[i+1];
    }

    return py::array_t<T>(shape, stride, vec.data());
}

// Convert py::array_t to C++ vector
template<typename T, typename InputType>
std::vector<T> pyarray2cppvector(InputType& pyarr) {
    PyObject* obj = pyarr.ptr();
    PyArrayObject* numpyobj = (PyArrayObject*) obj;
    int nd = numpyobj->nd;
    int size = 0; 
    for(int i=0; i<nd; i++) { size += numpyobj->dimensions[i]; }
    std::vector<T> cpparr((T*)numpyobj->data, (T*)numpyobj->data+size);
    return cpparr;
}

// A simple timer
template<typename D>
class Timer {
    public:
        using duration = std::chrono::duration<double, D>;
        std::chrono::time_point<std::chrono::high_resolution_clock> _start, _end;
        std::vector<duration> _diffs{0};
        void start() {_start = std::chrono::high_resolution_clock::now(); }
        void stop() {_end = std::chrono::high_resolution_clock::now(); _diffs.push_back(_end - _start);  }
        ssize_t niters() { return _diffs.size(); }
        double total() { return std::accumulate(_diffs.begin(), _diffs.end(), duration()).count(); }
};


Timer<std::milli> timer;
#define H 1024

int main() {
    py::scoped_interpreter guard{};

    // Load the Python module
    py::module calc = py::module::import("calc");
    py::object add  = calc.attr("add");
    py::object preprocess = calc.attr("preprocess");

    // Prepare the inputs
    std::vector<float> A(1*3*H*H, 100.0);
    std::vector<float> B(1*3*1*1, 50.0);
    std::vector<float> C(1*3*1*1, 0.5);
    auto pyA = cppvector2pyarray<float>(A, {1, 3, H, H});
    auto pyB = cppvector2pyarray<float>(B, {1, 3, 1, 1});
    auto pyC = cppvector2pyarray<float>(C, {1, 3, 1, 1});

    // warm up run
    py::object res4 = preprocess(pyA, pyB, pyC);

    // Benchmark
    for(int i=0; i<1e3; ++i) {
        timer.start();
        py::object res4 = preprocess(pyA, pyB, pyC);
        timer.stop();
    }
    std::cout << timer.total() / timer.niters() << std::endl;
}

Скомпилируйте его следующим образом:

g++ -O3 -Wall -std=c++14 -march=native -I pybind11/include -I /usr/lib/python2.7/dist-packages/numpy/core/include profile.cpp -o profile `python-config --libs --includes` -lpthread

Когда тестируется этот код C ++, требуется только ~ 9,5 мсек для одного только выполнения (исключая функции преобразования данных), что примерно в 3 раза медленнее по сравнению с тестированием IPython.


Среда

Python : v2.7 OS : Ubuntu 16.04


ОБНОВЛЕНИЕ 25-Июнь-2019

Пробовал тот же тест с Python 3.6, но результаты все те же.

...