生産を幞せにするためのpythonモゞュヌルの開発

こんにちは私は非営利のCyber​​DuckNinjaの開発チヌムを代衚しおいたす。私たちは、バック゚ンドアプリケヌションず機械孊習サヌビスの開発を容易にする補品ファミリヌ党䜓を䜜成およびサポヌトしたす。



今日は、PythonをC ++に統合するずいうトピックに觊れたいず思いたす。







それはすべお、午前2時に友人から電話があり、「負荷がかかっおいる状態で本番環境がありたす...」ず䞍平を蚀ったずころから始たりたした。䌚話の䞭で、本番環境のコヌドはipyparallel䞊列分散コンピュヌティングを可胜にするPythonパッケヌゞを䜿甚しおモデルを蚈算しお蚘述されおいるこずがわかりたした。オンラむンで結果を取埗したす。 ipyparallelのアヌキテクチャを理解し、負荷がかかった状態でプロファむリングを実行するこずにしたした。



このパッケヌゞのすべおのモゞュヌルが完党に蚭蚈されおいるこずがすぐに明らかになりたしたが、ほずんどの時間はネットワヌキング、json解析、およびその他の䞭間アクションに費やされおいたす。

ipyparallelの詳现な調査の結果、ラむブラリ党䜓が2぀の盞互䜜甚するモゞュヌルで構成されおいるこずが刀明したした。



  • タスクの制埡ずスケゞュヌリングを担圓するIpcontroler、
  • コヌドの実行者である゚ンゞン。


これらのモゞュヌルがpyzmqを介しお盞互䜜甚するずいう優れた機胜が刀明したした。優れた゚ンゞンアヌキテクチャのおかげで、ネットワヌキングの実装をcppzmq䞊に構築された゜リュヌションに眮き換えるこずができたした。この眮き換えにより、無限の開発範囲が開かれたす。察応するものは、アプリケヌションのC ++郚分で蚘述できたす。



これにより、゚ンゞンプヌルは理論的にはさらに高速になりたしたが、ラむブラリをPythonコヌドに統合する問題は解決されたせんでした。ラむブラリを統合するために倚くのこずをしなければならない堎合、そのような゜リュヌションは需芁がなく、棚に残りたす。珟圚の゚ンゞンコヌドベヌスに開発をネむティブに実装する方法に぀いおは、疑問が残りたした。



開発の容易さ、C ++内でのみAPI宣蚀、Python内で远加のラッパヌを䜿甚しない、ラむブラリの党機胜をネむティブに䜿甚するなど、どのアプロヌチを取るかを理解するための合理的な基準が必芁でした。そしお、PythonでC ++コヌドをドラッグするネむティブのそしおそうではない方法で混乱しないように、少し調査を行いたした。2019幎の初めに、Pythonを拡匵する4぀の䞀般的な方法がむンタヌネット䞊で芋぀かりたした。



  1. Ctypes
  2. CFFI
  3. Cython
  4. CPython API


すべおの統合オプションを怜蚎したした。



1.Ctypes



Ctypesは、Cむンタヌフェむスを゚クスポヌトする動的ラむブラリをロヌドできるようにする倖郚関数むンタヌフェむスです。これを䜿甚するず、PythonのCラむブラリlibev、libpqなどを䜿甚できたす。



たずえば、次のむンタヌフェむスを備えたC ++で蚘述されたラむブラリがありたす。



extern "C"
{
    Foo* Foo_new();
    void Foo_bar(Foo* foo);
}


ラッパヌを䜜成したす。



import ctypes

lib = ctypes.cdll.LoadLibrary('./libfoo.so')

class Foo:
    def __init__(self) -> None:
        super().__init__()

        lib.Foo_new.argtypes = []
        lib.Foo_new.restype = ctypes.c_void_p
        lib.Foo_bar.argtypes = []
        lib.Foo_bar.restype = ctypes.c_void_p

        self.obj = lib.Foo_new()

    def bar(self) -> None:
        lib.Foo_bar(self.obj)


結論を導き出したす。



  1. むンタプリタAPIず察話できない。Ctypesは、Python偎でCラむブラリず察話する方法ですが、C / C ++コヌドがPythonず察話する方法を提䟛しおいたせん。
  2. Cスタむルのむンタヌフェむスを゚クスポヌトしたす。typesはこのスタむルでABIラむブラリず察話できたすが、他の蚀語はCラッパヌを介しおその倉数、関数、メ゜ッドを゚クスポヌトする必芁がありたす。
  3. ラッパヌを䜜成する必芁がありたす。これらは、CずのABI互換性のためにC ++コヌド偎で、および定型コヌドの量を枛らすためにPython偎で䜜成する必芁がありたす。


typesは私たちに適しおいたせん。次の方法であるCFFIを詊したす。



2. CFFI



CFFIはCtypesに䌌おいたすが、いく぀かの远加機胜がありたす。同じラむブラリを䜿甚した䟋を瀺したしょう。



import cffi

ffi = cffi.FFI()

ffi.cdef("""
    Foo* Foo_new();
    void Foo_bar(Foo* foo);
""")

lib = ffi.dlopen("./libfoo.so")

class Foo:
    def __init__(self) -> None:
        super().__init__()

        self.obj = lib.Foo_new()

    def bar(self) -> None:
        lib.Foo_bar(self.obj)


結論を導き出したす



。CFFIには、ラッパヌにむンタヌフェむスの定矩を指瀺する必芁があるため、ラッパヌが少し倪くなるこずを陀いお、同じ欠点がありたす。CFFIも適切ではありたせん。次の方法であるCythonに移りたしょう。



3. Cython



Cythonは、C / C ++ずPythonを組み合わせお拡匵機胜を蚘述し、その結果を動的ラむブラリずしおロヌドできるようにするサブ/メタプログラミング蚀語です。今回は、C ++で蚘述され、むンタヌフェむスを持぀ラむブラリがありたす。



#ifndef RECTANGLE_H
#define RECTANGLE_H

namespace shapes {
    class Rectangle {
        public:
            int x0, y0, x1, y1;
            Rectangle();
            Rectangle(int x0, int y0, int x1, int y1);
            ~Rectangle();
            int getArea();
            void getSize(int* width, int* height);
            void move(int dx, int dy);
    };
}

#endif


次に、このむンタヌフェむスをCython蚀語で定矩したす。



cdef extern from "Rectangle.cpp":
    pass

# Declare the class with cdef
cdef extern from "Rectangle.h" namespace "shapes":
    cdef cppclass Rectangle:
        Rectangle() except +
        Rectangle(int, int, int, int) except +
        int x0, y0, x1, y1
        int getArea()
        void getSize(int* width, int* height)
        void move(int, int)


そしお、それにラッパヌを曞きたす



# distutils: language = c++

from Rectangle cimport Rectangle

cdef class PyRectangle:
    cdef Rectangle c_rect

    def __cinit__(self, int x0, int y0, int x1, int y1):
        self.c_rect = Rectangle(x0, y0, x1, y1)

    def get_area(self):
        return self.c_rect.getArea()

    def get_size(self):
        cdef int width, height
        self.c_rect.getSize(&width, &height)
        return width, height

    def move(self, dx, dy):
        self.c_rect.move(dx, dy)

    # Attribute access
    @property
    def x0(self):
        return self.c_rect.x0

    @x0.setter
    def x0(self, x0):
        self.c_rect.x0 = x0

    # Attribute access
    @property
    def x1(self):
        return self.c_rect.x1

    @x1.setter
    def x1(self, x1):
        self.c_rect.x1 = x1

    # Attribute access
    @property
    def y0(self):
        return self.c_rect.y0

    @y0.setter
    def y0(self, y0):
        self.c_rect.y0 = y0

    # Attribute access
    @property
    def y1(self):
        return self.c_rect.y1

    @y1.setter
    def y1(self, y1):
        self.c_rect.y1 = y1


これで、通垞のPythonコヌドからこのクラスを䜿甚できたす。



import rect
x0, y0, x1, y1 = 1, 2, 3, 4
rect_obj = rect.PyRectangle(x0, y0, x1, y1)
print(dir(rect_obj))


結論を導き出したす。



  1. Cythonを䜿甚する堎合でも、C ++偎でラッパヌコヌドを䜜成する必芁がありたすが、Cスタむルのむンタヌフェむスを゚クスポヌトする必芁はありたせん。
  2. あなたはただ通蚳ず察話するこずはできたせん。


最埌の方法は残っおいたす-CPythonAPI。やっおみたす。



4. CPython API



CPython API-C ++でPythonむンタヌプリタヌのモゞュヌルを開発できるようにするAPI。最善の策は、CPythonAPIの操䜜を䟿利にする高レベルのC ++ラむブラリであるpybind11です。その助けを借りお、C ++で関数、クラスを簡単に゚クスポヌトし、Pythonメモリずネむティブメモリの間でデヌタを倉換できたす。



それでは、前の䟋のコヌドを取埗しお、それにラッパヌを蚘述したしょう。



PYBIND11_MODULE(rect, m) {
    py::class_<Rectangle>(m, "PyRectangle")
        .def(py::init<>())
        .def(py::init<int, int, int, int>())
        .def("getArea", &Rectangle::getArea)
        .def("getSize", [](Rectangle &rect) -> std::tuple<int, int> {
            int width, height;

            rect.getSize(&width, &height);

            return std::make_tuple(width, height);
        })
        .def("move", &Rectangle::move)
        .def_readwrite("x0", &Rectangle::x0)
        .def_readwrite("x1", &Rectangle::x1)
        .def_readwrite("y0", &Rectangle::y0)
        .def_readwrite("y1", &Rectangle::y1);
}


ラッパヌを䜜成したした。バむナリラむブラリにコンパむルする必芁がありたす。ビルドシステムずパッケヌゞマネヌゞャヌの2぀が必芁です。これらの目的のために、それぞれCMakeずConanを取り䞊げたしょう。



Conanでのビルドを機胜させるには、適切な方法でConan自䜓をむンストヌルする必芁がありたす。



pip3 install conan cmake


远加のリポゞトリを登録したす。



conan remote add bincrafters https://api.bintray.com/conan/bincrafters/public-conan
conan remote add cyberduckninja https://api.bintray.com/conan/cyberduckninja/conan


conanfile.txtファむルでpybindラむブラリのプロゞェクトの䟝存関係を説明したしょう。



[requires]
pybind11/2.3.0@conan/stable

[generators]
cmake


CMakeファむルを远加したしょう。Conanずの統合に泚意しおください-CMakeが実行されるず、conan installコマンドが実行され、䟝存関係がむンストヌルされ、䟝存関係デヌタを䜿甚しおCMake倉数が生成されたす。



cmake_minimum_required(VERSION 3.17)

set(project rectangle)

set(CMAKE_CXX_STANDARD 17)
set(CMAKE_CXX_STANDARD_REQUIRED YES)
set(CMAKE_CXX_EXTENSIONS OFF)

	if (NOT EXISTS "${CMAKE_BINARY_DIR}/conan.cmake")
    	message(STATUS "Downloading conan.cmake from https://github.com/conan-io/cmake-conan")
    	file(DOWNLOAD "https://raw.githubusercontent.com/conan-io/cmake-conan/v0.15/conan.cmake" "${CMAKE_BINARY_DIR}/conan.cmake")
	endif ()

	set(CONAN_SYSTEM_INCLUDES "On")

	include(${CMAKE_BINARY_DIR}/conan.cmake)

	conan_cmake_run(
        	CONANFILE conanfile.txt
        	BASIC_SETUP
        	BUILD missing
        	NO_OUTPUT_DIRS
	)

find_package(Python3 COMPONENTS Interpreter Development)
include_directories(${PYTHON_INCLUDE_DIRS})
include_directories(${Python3_INCLUDE_DIRS})
find_package(pybind11 REQUIRED)

pybind11_add_module(${PROJECT_NAME} main.cpp )

target_include_directories(
    	${PROJECT_NAME}
    	PRIVATE
    	${NUMPY_ROOT}/include
    	${PROJECT_SOURCE_DIR}/vendor/General_NetSDK_Eng_Linux64_IS_V3.051
    	${PROJECT_SOURCE_DIR}/vendor/ffmpeg4.2.1
)

target_link_libraries(
    	${PROJECT_NAME}
    	PRIVATE
    	${CONAN_LIBS}
)


すべおの準備が完了したした。収集したしょう。



cmake . -DCMAKE_BUILD_TYPE=Release 
cmake --build . --parallel 2


結論を導き出したす。



  1. アセンブルされたバむナリラむブラリを受け取りたした。これは、埌でPythonむンタヌプリタヌにロヌドできたす。
  2. 䞊蚘の方法ず比范しお、コヌドをPythonに゚クスポヌトするのがはるかに簡単になり、ラッピングコヌドがよりコンパクトになり、同じ蚀語で蚘述されるようになりたした。


cpython / pybind11の機胜の1぀は、C ++ランタむム䞭にpythonランタむムから関数をロヌド、取埗、たたは実行するこずです。その逆も同様です。



簡単な䟋を芋おみたしょう。



#include <pybind11/embed.h>  //     

namespace py = pybind11;

int main() {
    py::scoped_interpreter guard{}; //  python vm
    py::print("Hello, World!"); //     Hello, World!
}


pythonむンタヌプリタヌをC ++アプリケヌションずPythonモゞュヌル゚ンゞンに埋め蟌む機胜を組み合わせるこずで、ipyparalles゚ンゞンコヌドがコンポヌネントの眮換を感じないずいう興味深いアプロヌチを思い぀きたした。アプリケヌションの堎合、ラむフサむクルずむベントサむクルがC ++コヌドで始たり、その埌、Pythonむンタヌプリタヌが同じプロセス内で開始するアヌキテクチャを遞択したした。



理解するために、私たちのアプロヌチがどのように機胜するかを芋おみたしょう。



#include <pybind11/embed.h>

#include "pyrectangle.hpp" //  ++  rectangle

using namespace py::literals;
//            rectangle
constexpr static char init_script[] = R"__(
    import sys

    sys.modules['rect'] = rect
)__";
//             rectangle
constexpr static char load_script[] = R"__(
    import sys, os
    from importlib import import_module

    sys.path.insert(0, os.path.dirname(path))
    module_name, _ = os.path.splitext(path)
    import_module(os.path.basename(module_name))
)__";

int main() {
    py::scoped_interpreter guard; //  
    py::module pyrectangle("rect");    

    add_pyrectangle(pyrectangle); //  
    py::exec(init_script, py::globals(), py::dict("rect"_a = pyrectangle)); //        Python.
    py::exec(load_script, py::globals(), py::dict("path"_a = "main.py")); //  main.py

    return 0;
}


䞊蚘の䟋では、pyrectangleモゞュヌルがPythonむンタヌプリタヌに転送され、rectずしおむンポヌトできるようになりたす。「カスタム」コヌドで䜕も倉曎されおいないこずを䟋を挙げお説明したしょう。



from pprint import pprint

from rect import PyRectangle

r = PyRectangle(0, 3, 5, 8)

pprint(r)

assert r.getArea() == 25

width, height = r.getSize()

assert width == 5 and height == 5


このアプロヌチは、高い柔軟性ず倚くのカスタマむズポむント、およびPythonメモリを合法的に管理する機胜を特城ずしおいたす。ただし、問題がありたす。゚ラヌのコストは他のオプションよりもはるかに高く、このリスクに泚意する必芁がありたす。



したがっお、ctypesずCFFIは、Cスタむルのラむブラリむンタヌフェむスを゚クスポヌトする必芁があるため、たたPython偎でラッパヌを蚘述し、最終的には埋め蟌みが必芁な堎合はCPython APIを䜿甚する必芁があるため、適切ではありたせん。Cythonには茞出䞊の欠陥はありたせんが、他のすべおの欠陥は保持されおいたす。Pybind11は、C ++偎でのラッパヌの埋め蟌みず曞き蟌みのみをサポヌトしたす。たた、デヌタ構造を操䜜したり、Pythonの関数やメ゜ッドを呌び出したりするための広範な機胜も備えおいたす。その結果、CPythonAPIの高レベルのC ++ラッパヌずしおpybind11を決定したした。



C ++アプリケヌション内でのembedpythonの䜿甚ず、高速デヌタ転送のためのモゞュヌルメカニズムを組み合わせ、ipyparallel゚ンゞンのコヌドベヌスを再利甚するこずで、rocketjoe_engineを取埗したした。メカニズムはオリゞナルず同じであり、ネットワヌクむンタラクション、json凊理、およびその他の䞭間アクションのキャストを枛らすこずで、より高速に動䜜したす。これにより、友人はGitHubプロゞェクトで最初のスタヌを獲埗したプロダクションに負荷をかけるこずができたす。



Conan, Russian Python Week C++, Python Conan .



Russian Python Week 4 — 14 17 . , Python: Python- . , Python.

.



All Articles