• 【Faster RCNN系列】cython distutils 以及setup


      

    setup这一年也是遇到了很多次,随着python编程学习的不断深度对于python的了解也不断在增加,这里做一次简单的小节。

    相关工具:distutils,cython

    1.Cython简介

    我们平时使用的python,又叫CPython,因为他是用C语言写的,一般来说,我们的python源代码(.py沃森件),首先编译成字节码(.pyc文件),然后将.pyc文件放在python虚拟机上运行,这里的python虚拟机就是所谓的“python解释器”。总而言之,纯python代码的运行速度介于传统的编译语言和传统的解释语言之间。

    2.1什么是字节码?

    字节码在python虚拟机程序里对应的是PyCodeObject对象; .pyc文件是字节码在磁盘上的表现形式。

    每一个以py结尾的python源代码文件都是模块,其中那个启动后能够运行整个程序的文件叫顶层文件。而顶层文件导入其他模块(文件),必须找到文件然后将其编译成字节码,并且运行字节码。

    导入文件时编译的字节码会自动保存,同时保存的还有时间戳。如果同时存在.py和.pyc,python会使用.pyc运行,如果.pyc的编译时间早于.py的时间,则重新编译.py,并更新.pyc文件。

    如果python无法在机器上写入字节码,程序仍然可以工作,字节码会在内存中生成并在程序结束时丢弃掉。(严格而讲,只有文件导入的情况下字节码才会保存,并不是对顶层文件)。

    如果想生成test.pyc,我们可以使用python内置模块py_compile来编译。也可以执行命令 python -m test.py 这样,就生成了test.pyc。

    typedef struct {
        PyObject_HEAD
        int co_argcount;        /* 位置参数个数 */
        int co_nlocals;         /* 局部变量个数 */
        int co_stacksize;       /* 栈大小 */
        int co_flags;   
        PyObject *co_code;      /* 字节码指令序列 */
        PyObject *co_consts;    /* 所有常量集合 */
        PyObject *co_names;     /* 所有符号名称集合 */
        PyObject *co_varnames;  /* 局部变量名称集合 */
        PyObject *co_freevars;  /* 闭包用的变量名集合 */
        PyObject *co_cellvars;  /* 内部嵌套函数引用的变量名集合 */
        /* The rest doesn’t count for hash/cmp */
        PyObject *co_filename;  /* 代码所在文件名 */
        PyObject *co_name;      /* 模块名|函数名|类名 */
        int co_firstlineno;     /* 代码块在文件中的起始行号 */
        PyObject *co_lnotab;    /* 字节码指令和行号的对应关系 */
        void *co_zombieframe;   /* for optimization only (see frameobject.c) */
    } PyCodeObject;

    2.2执行字节码?

    Python虚拟机的原理就是模拟可执行程序再X86机器上的运行,X86的运行时栈帧如下图:

    当发生函数调用时,创建新的栈帧,对应Python的实现就是PyFrameObject对象。PyFrameObject对象创建程序运行时的动态信息,即执行环境,相关源码大致如下:

    typedef struct _frame{  
        PyObject_VAR_HEAD //"运行时栈"的大小是不确定的  
        struct _frame *f_back; //执行环境链上的前一个frame,很多个PyFrameObject连接起来形成执行环境链表  
        PyCodeObject *f_code; //PyCodeObject 对象,这个frame就是这个PyCodeObject对象的上下文环境  
        PyObject *f_builtins; //builtin名字空间  
        PyObject *f_globals;  //global名字空间  
        PyObject *f_locals;   //local名字空间  
        PyObject **f_valuestack; //"运行时栈"的栈底位置  
        PyObject **f_stacktop;   //"运行时栈"的栈顶位置  
        //...  
        int f_lasti;  //上一条字节码指令在f_code中的偏移位置  
        int f_lineno; //当前字节码对应的源代码行  
        //...  
          
        //动态内存,维护(局部变量+cell对象集合+free对象集合+运行时栈)所需要的空间  
        PyObject *f_localsplus[1];    
    } PyFrameObject;

    每一个 PyFrameObject对象都维护了一个 PyCodeObject对象,这表明每一个 PyFrameObject中的动态内存空间对象都和源代码中的一段Code相对应。

    2.2什么是Cython?

    Cython是Python语言的扩展模块,他的目的在于称为python语言的超集(superset),为python提供高级的,面向对象的,函数式的和动态的编程。他的主要功能是支持(可选)部分静态类型的声明作为Cython语言的一部分。这样cython的源代码就可以被转化为优化过的C/C++代码,然后可以将这些代码编程称为python的扩展模块。Cython代码在CPython运行时环境中执行,但是以编译的C的速度执行,并且能够直接调用C库。同时,它保留了Python源代码的原始接口,这使得它可以直接从Python代码中使用。

    2.3构建Cython

    Cython代码必须编译,具体包括两步:

      第一步,将A.pyx文件用Cython编译到一个.c文件中,其中含有python扩展模块的代码

      第二步,将.c文件编译成.so文件(Windows下为.pyd文件),文件可以 直接import进入Python session。Distutils或setuptools负责这部分。虽然Cython可以在某些情况下为你调用它们。

    具体实例,在faser rcnn中,bbox_overlaps函数就行用cython写的。

     

      具体构建Cython的几种方法:

        1.写一个distutils / setuptools setup.py。

    setyp.py:

    from distutils.core import setup
    from Cython.Build import cythonize
    
    setup(
        ext_modules = cythonize("helloworld.pyx")
    )

    命令行:

    $ python setup.py build_ext --inplace

        2.使用Pyximport,导入Cython .pyx文件就像它们是.py文件一样(使用distutils在后台编译和构建)。这种方法比编写一个setup.py容易一些。但不是很灵活,比如,您需要某些编译选项。(其实,没有学习编译原理的我不需要哪些编译选项,显然这种方法是极好的)。但是,我的电脑在使用这种方法的时候,失败了,可能是VS环境的原因吧,所以我采用了第一种方法,直接编译了一个pyd文件使用。

    import pyximport; pyximport.install()
    import helloworld
    # Hello World

        3.Jupyter notebook允许内联Cython代码。这是开始编写Cython代码并运行它的最简单方法。

    2.distutils简介

    除了Cython中使用到setup.py,编写python的第三方库,也是要编写setup.py的。其实如果我们下载过一些第三库的源代码文件,打开之后一般就会有一个setup.py,执行python setup.py install 就可以安装这个库了。setup.py 如何编写内容很多,可以参考官方文档:https://wiki.python.org/moin/Distutils/Tutorial?highlight=%28setup.py%29。一个典型的setup.py的写法如下(参考自官方文档):

    文件结构为:
    top
    |-- package
    |   |-- __init__.py  # 空的
    |   |-- module.py  # 这个package中的模块文件
    |   `-- things
    |       |-- cross.png
    |       |-- fplogo.png
    |       `-- tick.png
    |-- README
    `-- setup.py

    setup.py:

    from distutils.core import setup
    
    #This is a list of files to install, and where
    #(relative to the 'root' dir, where setup.py is)
    #You could be more specific.
    files = ["things/*"]
    
    setup(name = "appname",
        version = "100",
        description = "yadda yadda",
        author = "myself and I",
        author_email = "email@someplace.com",
        url = "whatever",
        #Name the folder where your packages live:
        #(If you have other packages (dirs) or modules (py files) then
        #put them into the package directory - they will be found 
        #recursively.)
        packages = ['package'],
        #'package' package must contain files (see list above)
        #I called the package 'package' thus cleverly confusing the whole issue...
        #This dict maps the package name =to=> directories
        #It says, package *needs* these files.
        package_data = {'package' : files },
        #'runner' is in the root.
        scripts = ["runner"],
        long_description = """Really long text here.""" 
        #
        #This next part it for the Cheese Shop, look a little down the page.
        #classifiers = []     
    ) 

     Making the installation tarball

    python setup.py sdist

    我的编译过程:

    setup.py:

    import sys
    import numpy as np
    A=sys.path.insert(0, "..")
    from distutils.core import setup
    from distutils.extension import Extension
    from Cython.Build import cythonize
    from Cython.Distutils import build_ext
    
    # ext_module = cythonize("TestOMP.pyx")
    ext_module = Extension(
                            "bbox",
                ["bbox.pyx"],
                extra_compile_args=["/openmp"],
                extra_link_args=["/openmp"],
                )
    
    setup(
        cmdclass = {'build_ext': build_ext},
        ext_modules = [ext_module],
        #注意这一句一定要有,不然只编译成C代码,无法编译成pyd文件
        include_dirs=[np.get_include()]
    )

    Cython官网教程:http://docs.cython.org/en/latest/index.html(中文版:https://moonlet.gitbooks.io/cython-document-zh_cn/content/ch1-basic_tutorial.html)

     参考博客:   https://www.cnblogs.com/webber1992/p/6597166.html

          https://blog.csdn.net/runner668/article/details/80137962

          https://www.cnblogs.com/lyrichu/p/6818008.html

          https://www.cnblogs.com/lyrichu/p/6818008.html

          https://www.cnblogs.com/UnGeek/p/5922630.html

  • 相关阅读:
    要如何用[ZT]sendmessage來切換PageControl1 的TabSheet2呢
    Delphi 常用API 函数
    [ZT]如何得到其他程序的Richedit中的RTF数据
    转帖一篇关于DELPHI调试的文章AQTime
    讀取股票資料檔與指標計算方法之封裝
    GC的三代回收机制
    SQL语句的解析过程
    .Net 垃圾回收机制整理
    美国人吃了交通罚单怎么办?
    Ihttphandler,Ihttpmodule
  • 原文地址:https://www.cnblogs.com/SsoZhNO-1/p/9987515.html
Copyright © 2020-2023  润新知