zoukankan      html  css  js  c++  java
  • 如何用C++ 写Python模块扩展(二)

    Python模块包含的类创建(下)

    1. 类的方法表创建

      • 直接上代码

           static PyMethodDef VCam_MethodMembers[] =      //类的所有成员函数结构列表同样是以全NULL结构结束
          {
              { "set_fill", (PyCFunction)VCam_SetFill, METH_VARARGS, "Set video resize method (0: Aspect fit, 1: Aspect fill, 2: Stretch), used when input frame size differs from VCam output size." },
              { "mirror", (PyCFunction)VCam_Mirror, METH_VARARGS, "Mirror the output video (0: no mirror, others: mirror), non-persistent." },
              { "rotate", (PyCFunction)VCam_Rotate, METH_VARARGS, "Rotate the input video 90 degree (0: no rotate, others: rotate), non-persistent." },
              { "flip", (PyCFunction)VCam_Flip, METH_VARARGS, "Vertical flip the output video(0: no flip, others : flip), non - persistent." },  
              { "send_image", (PyCFunction)VCam_SendImg, METH_VARARGS, "Display a image( path) to vCam." },
              { "capture_screen", (PyCFunction)VCam_CaptureScreen, METH_VARARGS, "Capture region of screen and set it as VCam output." },
              { NULL, NULL, NULL, NULL }
          };
        
      • PyMethondDef 结构的定义

          struct PyMethodDef {
              const char  *ml_name;   /* The name of the built-in function/method */
              PyCFunction ml_meth;    /* The C function that <isindex></isindex>mplements it */
              int         ml_flags;   /* Combination of METH_xxx flags, which mostly
                                         describe the args expected by the C func */
              const char  *ml_doc;    /* The __doc__ attribute, or NULL */
          };
          typedef struct PyMethodDef PyMethodDef;
        
          #define PyCFunction_New(ML, SELF) PyCFunction_NewEx((ML), (SELF), NULL)
          PyAPI_FUNC(PyObject *) PyCFunction_NewEx(PyMethodDef *, PyObject *,
                                                   PyObject *);
        
          /* Flag passed to newmethodobject */
          /* #define METH_OLDARGS  0x0000   -- unsupported now */
          #define METH_VARARGS  0x0001
          #define METH_KEYWORDS 0x0002
          /* METH_NOARGS and METH_O must not be combined with the flags above. */
          #define METH_NOARGS   0x0004
          #define METH_O        0x0008
        
          /* METH_CLASS and METH_STATIC are a little different; these control
             the construction of methods for a class.  These cannot be used for
             functions in modules. */
          #define METH_CLASS    0x0010
          #define METH_STATIC   0x0020
        
          /* METH_COEXIST allows a method to be entered even though a slot has
             already filled the entry.  When defined, the flag allows a separate
             method, "__contains__" for example, to coexist with a defined
             slot like sq_contains. */
        
          #define METH_COEXIST   0x0040
        
      • 其他没啥好说的结构定义已经很明白了,就是第三个元素ml_falg 需要根据函数时机传入参数要求进行调整 就说几个常用的flag 其他见手册

        • METH_NOARGS 表示没有参数传入,
        • METH_KEYWORDS 表示传入keyword参数
        • METH_VARARGS 表示传入位置参数
        • 部分flag可以组合传入如 METH_VARARGS|METH_KEYWORDS
        • 注意METH_NOARGS 不能与 前面两个flag组合使用
    2. 写类的内置属性信息表说明PyTypeObject实例VCam_ClassInfo

      • 直接上代码,代码中包含了PyTypeObject结构体 大部分元素,具体见object.h 头文件定义

          static PyTypeObject VCam_ClassInfo =
          {
              PyVarObject_HEAD_INIT(NULL, 0)
              "PyVcam.VCam",            //可以通过__class__获得这个字符串. CPP可以用类.__name__获取.   const char *
              sizeof(VCam),                 // tp_basicsize 类/结构的长度.调用PyObject_New时需要知道其大小.  Py_ssize_t
              0,                              //tp_itemsize  Py_ssize_t
              (destructor)VCam_Destruct,    //类的析构函数.      destructor
              0,                            //类的print 函数      printfunc
              0,                               //类的getattr 函数  getattrfunc
              0,                              //类的setattr 函数   setattrfunc
              0,                              //formerly known as tp_compare(Python 2) or tp_reserved (Python 3)  PyAsyncMethods *
              0,          //tp_repr 内置函数调用。    reprfunc
              0,                              //tp_as_number   指针   PyNumberMethods *
              0,                              //tp_as_sequence 指针   PySequenceMethods *
              0,                              // tp_as_mapping 指针  PyMappingMethods *
              0,                              // tp_hash   hashfunc
              0,                              //tp_call     ternaryfunc
              0,          //tp_str/print内置函数调用.   reprfunc
              0,                          //tp_getattro    getattrofunc
              0,                          //tp_setattro     setattrofunc
              0,                          //tp_as_buffer 指针 Functions to access object as input/output buffer   PyBufferProcs
              Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE,                 //tp_flags    如果没有提供方法的话,为Py_TPFLAGS_DEFAULE     unsigned long
              "VCam Module write by C++!",                   // tp_doc  __doc__,类/结构的DocString.  const char *
              0,                                                   //tp_traverse    call function for all accessible objects    traverseproc
              0,                                                      // tp_clear   delete references to contained objects    inquiry
              0,                                                      //  tp_richcompare   richcmpfunc
              0,                                                      //tp_weaklistoffset  Py_ssize_t
              0,                                                      // tp_iter  getiterfunc
              0,                                          //tp_iternext    iternextfunc
        
        
              /* Attribute descriptor and subclassing stuff */
        
              VCam_MethodMembers,        //类的所有方法集合.   PyMethodDef *
              VCam_DataMembers,          //类的所有数据成员集合.  PyMemberDef *
              0,                                              // tp_getset   PyGetSetDef *
              0,                                              //  tp_base   _typeobject *
              0,                                              //  tp_dict     PyObject *
              0,                                              // tp_descr_get   descrgetfunc
              0,                                                          //tp_descr_set   descrsetfunc
              0,                                              //tp_dictoffset   Py_ssize_t
              (initproc)VCam_init,      //类的构造函数.tp_init       initproc
              0,                      //tp_alloc  allocfunc
              0,                          //tp_new   newfunc
              0,                        // tp_free    freefunc
              0,                      //  tp_is_gc     inquiry
          };
        
      • VCam_ClassInfo 中把前面所创建的 init函数、析构函数、方法表、成员表等加入类信息表

    模块创建和初始化

    1. 创建模块信息

      • 直接上代码

          static PyModuleDef ModuleInfo =
           {
           PyModuleDef_HEAD_INIT,
           "PyVcam",               //模块的内置名--__name__.
           NULL,                 //模块的DocString.__doc__
           -1,
           NULL, NULL, NULL, NULL, NULL
           };
        
      • PyModuleDef 结构题定义

          typedef struct PyModuleDef{
            PyModuleDef_Base m_base;
            const char* m_name;
            const char* m_doc;
            Py_ssize_t m_size;
            PyMethodDef *m_methods;
            struct PyModuleDef_Slot* m_slots;
            traverseproc m_traverse;
            inquiry m_clear;
            freefunc m_free;
          } PyModuleDef;
        
    2. 初始化模块

      • 先上代码

          PyMODINIT_FUNC PyInit_PyVcam(void)       //模块外部名称为--PyVcam
          {
              
              Gdiplus::GdiplusStartupInput StartupInput;
              GdiplusStartup(&m_gdiplusToken, &StartupInput, NULL);
        
              PyObject* pReturn = 0;
              VCam_ClassInfo.tp_new = PyType_GenericNew;       //此类的new内置函数—建立对象.
              
              
              if (PyType_Ready(&VCam_ClassInfo) < 0)
                  return NULL;
        
              pReturn = PyModule_Create(&ModuleInfo);
              if (pReturn == NULL)
                  return NULL;
              Py_INCREF(&VCam_ClassInfo);
              PyModule_AddObject(pReturn, "VCam", (PyObject*)&VCam_ClassInfo); //将这个类加入到模块的Dictionary中.
              
              return pReturn;
          }
        
      • 代码解释:

        • Python模块必须要导出一个返回值为PyObject*名为PyInit_XXX的函数用来初始化模块信息,Python加载模块时候回去直接调用此函数来初始化。
        • PyMODINIT_FUNC 宏其实就是以下语句: __declspec(dllexport) PyObject*
        • VCam_ClassInfo.tp_new = PyType_GenericNew; 这条语句其实可以不用写直接在前面VCam_Classinfo里面对应位置加入PyType_GenericNew 即可,想想找到对应那个置要找到眼花 干脆直接以这种形式写出来;反之前面整个VCam_Classinfo 后面的结构其实可以不写,直接以VCam_Classinfo.xxx =xxx的形式写出
        • 调用一个PyType_Ready (&VCam_ClassInfo)来完成类的定义
        • 然后用PyModule_Create(&ModuleInfo) 创建Module
        • 调用PyModule_AddObject将 Vcam类加入到Module 中 同时别忘了增加类体引用计数
        • 将模块返回给Python 大功告成

    PythonC扩展的执行效率问题(GIL)

    1. GIL问题
      • GIL锁原理

          for (;;) {
              if (--ticker < 0) {   //这是之前版本的GIL锁原理 执行check_interval条数指令 放一次GIL 貌似现在版本不再按照指令条数来放锁了而是按照时间间隔
                  ticker = check_interval;             
                  /* Give another thread a chance */
                  PyThread_release_lock(interpreter_lock);   //释放 GIL         
                  /* Other threads may run now */             
                  PyThread_acquire_lock(interpreter_lock, 1); //立马重新申请GIL 一放一抢 其他线程就有机会
              }             
              bytecode = *next_instr++;  //这里读入python指令
              switch (bytecode) {  
                  /* execute the next instruction ... */  //执行指令
              }
          }
        
      • 由于CPython GIL存在在进行多线程任务时 python指令在执行时会一直占着GIL导致其他线程一直在等着抢锁 于是多线程就编程了单线程,无论你开多少个线程貌似都只能同时有一个线程在运行

    2. GIL锁问题的解决
      在纯Python环境下CPython的GIL貌似无解了,但是GIL真的无解了么?
      • 大家都知道IO密集型场景利用多线程能显著提高执行效率,也就是说IO任务执行过程中释放了GIL 显然这个释放肯定不是在ticker<0时释放的, IO任务到底是怎么释放GIL的呢

        • IO任务释放原理如下

            /* s.connect((host, port)) method */
            static PyObject *
            sock_connect(PySocketSockObject *s, PyObject *addro)
            {
                sock_addr_t addrbuf;
                int addrlen;
                int res;
             
                /* convert (host, port) tuple to C address */
                getsockaddrarg(s, addro, SAS2SA(&addrbuf), &addrlen);
             
                Py_BEGIN_ALLOW_THREADS
                res = connect(s->sock_fd, addr, addrlen);
                Py_END_ALLOW_THREADS
             
                /* error handling and so on .... */
            }
          
        • 上面是部分socket代码,可以看到在执行 connect之前 调用了一个宏 Py_BEGIN_ALLOW_THREADS 这个宏就是用来释放GIL的 成功connect后又调用 Py_END_ALLOW_THREADS重新申请GIL

        • 解决Python多线程运行效率问题似乎有门

      • Python计算密集型不能用多线程?似乎利用C++写一个模块来处理计算任务多线程照样能达到并行效果

        • 下面就开始写代码验证这个问题
          • 在c++模块中写了两个计算密集型函数,函数计算返回之类的算法都没有区别唯一区别就是: 其中一个函数在高密度计算前释放了GIL计算完成后重新申请锁

            static PyObject* Gil_free(GilTest* self,PyObject* args){    
               LONGLONG num;
               if (!PyArg_ParseTuple(args, "L", &num))return NULL;
               
               LONGLONG rst;
               Py_BEGIN_ALLOW_THREADS
                   for (LONGLONG i = 1; i <= num * 100; i++)
                   {
                       for (LONGLONG j = 1; j <= num * 100; j++)
                       {
                           rst = i*j;
                       }
                   }                
               Py_END_ALLOW_THREADS
                   return Py_BuildValue("i", rst);
            

            }
            static PyObject* Gil_lock(GilTest* self, PyObject* args){
            LONGLONG num;
            if (!PyArg_ParseTuple(args, "L", &num))return NULL;
            LONGLONG rst;
            for (LONGLONG i = 1; i <= num * 100; i++)
            {
            for (LONGLONG j = 1; j <= num * 100; j++)
            {
            rst = i*j;
            }
            }
            return Py_BuildValue("i", rst);
            }

        • 将函数封装到一个python模块中调用模块 写一个脚本开多线程执行

            from GilTest import GilTest
            import time
            from threading import Thread
          
          
            def foo(num, i, start):
                obj = GilTest()
                obj.compute_with_gil(num)   # 调用的函数计算时没有释放GIL
                print("foo %s is over" % i, time.time() - start)
          
          
            def bar(num, i, start):
                obj = GilTest()
                obj.compute_without_gil(num)   # 调用的函数计算时释放GIL
                print("bar %s is over" % i, time.time() - start)
          
          
            def run():
                print("stat foo")
                start = time.time()  # 开foo线程开始计时
                thread_list1 = []
                for i in range(10):
                    thread_list1.append(Thread(target=foo, args=(1000, i, start)))
                for i in thread_list1:
                    i.start()
                for i in thread_list1:
                    i.join()
                print("stat bar")
                time.sleep(1) 
                start = time.time()   # 开bar线程开始计时
                thread_list2 = []
                for i in range(10):
                    thread_list2.append(Thread(target=bar, args=(1000, i, start)))
                for i in thread_list2:
                    i.start()
                for i in thread_list2:
                    i.join()
          
          
            if __name__ == '__main__':
          
                run()
          
        • 输出执行结果

            stat foo
            foo 0 is over 2.2932560443878174
            foo 1 is over 4.577575445175171
            foo 2 is over 6.859208583831787
            foo 3 is over 9.145148277282715
            foo 4 is over 11.43115520477295
            foo 5 is over 13.71883225440979
            foo 6 is over 15.999829292297363
            foo 7 is over 18.281397581100464
            foo 8 is over 20.57776975631714
            foo 9 is over 22.851707935333252
            stat bar
            bar 3 is over 4.594241380691528
            bar 6 is over 4.594241380691528
            bar 7 is over 4.609868288040161
            bar 2 is over 4.63910174369812
            bar 8 is over 5.750362157821655
            bar 4 is over 5.765988826751709
            bar 5 is over 5.859748840332031
            bar 0 is over 5.859748840332031
            bar 1 is over 5.859748840332031
            bar 9 is over 5.937881946563721
          
            Process finished with exit code 0
          
        • 可以发现foo线程完全像是在运行单线程,每个线程执行完成时间比上一个线程大约多2.3秒看;而bar线程是真正的多线程 ,线程完成计算时间差别很小,而且完成先后顺序是乱序的,因为CPU是四核的所以线程之间还是会存在抢cpu情况,每个线程运行时间较foo要长一点(foo每个线程的运算几乎是独占运行)

        • 再来看看CPU占用

        在执行foo时python进程占用CPU约15%作用,当程序执行到bar线程时可以看到python进程cpu占用直线上飙到接近100%的占用,这也说明了此时python的线程是并行的。

    3. 释放GIL的注意事项
      前面演示似乎解决python多线程GIL问题,执行效率很高,是不是意味着不论啥玩意拿过来直接释放GIL就好了呢,显然不可能是这样的,考虑下GIL为啥存在吧,在c++中释放GIL需要考虑以下问题:
      • 首先要预估函数执行需要多少时间(需要几个周期),若代码几部就能走完那么如果此时释放GIL不但不能提高效率反而会降低代码执行效率,因为释放和申请GIL也是有系统开销的
      • 如果C++代码存在读取或操作PyObject的代码那么在执行这些代码时必须确保线程持有GIL锁
      • 若在函数内释放了GIL锁函数返回前必须重新申请GIL锁,否则将带来灾难性后果

    如何用C++ 写Python模块扩展(一)
    模块C++源码下载
    好了先写到这请期待后续的大招 封装windows系统的com组件到python模块

  • 相关阅读:
    梯度下降进阶
    梯度下降基础
    python---matplotlib
    python---numpy
    浅析Jupyter Notebook
    anaconda安装
    机器学习---导学
    python---线程与进程
    mapping values are not allowed in this context at line 115 column 10
    laravel进行单元测试的时候如何模拟数据库以及mockery的调用
  • 原文地址:https://www.cnblogs.com/RexShao/p/8453032.html
Copyright © 2011-2022 走看看