C / C ++ de Python (boost)

principal

Le dernier article de la série sur la façon d'invoquer C / C ++ à partir de Python3 est passé par toutes les façons connues de le faire. Cette fois, j'ai pu booster . Qu'est-ce qui en est ressorti ci-dessous.


C


Je prends le même exemple de bibliothèque de test comme base et en fais une variation pour une méthode spécifique. Une bibliothèque de tests pour montrer comment travailler avec des variables globales, des structures et des fonctions avec des arguments de différents types.


test.c:


#include "test.hpp" int a = 5; double b = 5.12345; char c = 'X'; int func_ret_int(int val) { printf("C get func_ret_int: %d\n", val); return val; } double func_ret_double(double val) { printf("C get func_ret_double: %f\n", val); return val; } object func_ret_str(char *val) { printf("C get func_ret_str: %s\n", val); return object(string(val)); } char func_many_args(int val1, double val2, char val3, short val4) { printf("C get func_many_args: int - %d, double - %f, char - %c, short - %d\n", val1, val2, val3, val4); return val3; } test_st_t * func_ret_struct(test_st_t *test_st) { if (test_st) { printf("C get test_st: val1 - %d, val2 - %f, val3 - %c\n", test_st->val1, test_st->val2, test_st->val3); } return test_st; } // _test    BOOST_PYTHON_MODULE(_test) { /* *   */ def("func_ret_int", func_ret_int); def("func_ret_double", func_ret_double); def("func_ret_str", &func_ret_str); def("func_many_args", func_many_args); //   // manage_new_object C     // reference_existing_object C     def("func_ret_struct", &func_ret_struct, return_value_policy<reference_existing_object>()); /* *    */ scope().attr("a") = a; scope().attr("b") = b; scope().attr("c") = c; /* *  */ class_<test_st_t>("test_st_t") .def_readwrite("val1", &test_st_t::val1) .def_readwrite("val2", &test_st_t::val2) .def_readwrite("val3", &test_st_t::val3) ; } 

test.h:


 using namespace boost::python; using namespace std; #ifdef __cplusplus extern "C" { #endif typedef struct test_st_s test_st_t; typedef char * char_p; extern int a; extern double b; extern char c; int func_ret_int(int val); double func_ret_double(double val); object func_ret_str(char *val); char func_many_args(int val1, double val2, char val3, short val4); test_st_t *func_ret_struct(test_st_t *test_st); struct test_st_s { int val1; double val2; char val3; }; #ifdef __cplusplus } #endif 

Comment compiler:


 g++ -g -fPIC -I/usr/include/python3.6 -I./src/c -o ./objs/test.o -c ./src/c/test.cpp g++ -fPIC -g -shared -o ./lib/_test.so ./objs/test.o -lboost_python3 

La source se compile dans une bibliothèque dynamique.
python boost est similaire à pybind11 , vous devez également décrire les fonctions que python verra. Mais à mon avis, le boost est plus volumineux et complexe. Par exemple:


 def("func_ret_struct", &func_ret_struct, return_value_policy<reference_existing_object>()); 

La fonction func_ret_struct prend un pointeur sur une structure comme argument et retourne le même pointeur. Pour cela, vous devez spécifier les règles de l'objet retourné return_value_policy <reference_existing_object> () . reference_existing_objec indique que l'objet renvoyé existait déjà. Si vous spécifiez manage_new_object, cela signifie que nous renvoyons un nouvel objet. Dans ce cas, un tel script tombera dans une erreur de segmentation sur le garbage collector:


 test_st = _test.test_st_t() ret = _test.func_ret_struct(test_st) 

Parce que le garbage collector effacera d'abord les données que contient test_st, puis souhaite effacer les données que contient l'objet ret. Qui contient les mêmes données que contenait test_st, mais elles ont déjà été effacées.


Il est intéressant de savoir comment dans ce cas décrire une telle fonction (n'est pas allé en profondeur)?:


 test_st_t * func_ret_struct(test_st_t *test_st) { if (test_st) { return test_st; } else { return (test_st_t *) malloc(sizeof(test_st_t)); } } 

Une telle fonction peut renvoyer un objet existant aussi bien qu'un objet existant.


J'ai également eu un problème avec une telle fonction:


 char * func_ret_str(char *val) { return val; } 

Si je comprends bien, vous ne pouvez pas obtenir un pointeur vers un type de données standard à partir de python dans boost. Il n'est possible que sur la structure , la classe et l' union . Si quelqu'un connaît un moyen d'éclairer.


Python


Pour python, le module devient natif.
main.py:


 #!/usr/bin/python3 #-*- coding: utf-8 -*- import sys import time #    test #sys.path.append('.') sys.path.append('lib/') #   import _test ### ## C ### print("boost\n") print("C\n") start_time = time.time() ## #    ## print('  :') print('ret func_ret_int: ', _test.func_ret_int(101)) print('ret func_ret_double: ', _test.func_ret_double(12.123456789)) print('ret func_ret_str: ', _test.func_ret_str('Hello!')) print('ret func_many_args: ', _test.func_many_args(15, 18.1617, 'X', 32000)) ## #    ## print('\n  :') print('ret a: ', _test.a) #   . _test.a = 22 print('new a: ', _test.a) print('ret b: ', _test.b) print('ret c: ', _test.c) ## #    ## print('\n  :') #      test_st = _test.test_st_t() test_st.val1 = 5 test_st.val2 = 5.1234567 test_st.val3 = 'Z' print('val1 = {}\nval2 = {}\nval3 = {}'.format(test_st.val1, test_st.val2, test_st.val3)) ret = _test.func_ret_struct(test_st) #    C print('ret val1 = {}\nret val2 = {}\nret val3 = {}'.format(ret.val1, ret.val2, ret.val3)) #   print("--- {} seconds ---".format(time.time() - start_time)) 

Avantages et inconvénients du boost


Avantages :


  • syntaxe simple lorsqu'elle est utilisée en Python

Inconvénients :


  • vous devez modifier les sources C ++ ou écrire une liaison pour elles
  • booster seul n'est pas facile

Le code, comme d'habitude, j'essaie de commenter clairement.


Le temps d'exécution moyen des tests sur chaque méthode avec 1000 démarrages:


  • ctypes: - 0,0004987692832946777 secondes ---
  • CFFI: - 0,00038521790504455566 secondes ---
  • pybind: - 0,0004547207355499268 secondes ---
  • API C: - 0,0003561973571777344 secondes ---
  • boost: - 0,00037789344787597656 secondes ---

Les références


Source: https://habr.com/ru/post/fr471618/


All Articles