热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

Cython的用法以及填坑姿势

因为项目需要,需要优化已有的Python代码。目前Python代码的执行过程是将Python代码转变成一行行指令,然后解释器解释指令的执行,调用到C代码层。如果去掉指令解释这个阶段,直接进入C代码层,

因为项目需要,需要优化已有的Python代码。目前Python代码的执行过程是将Python代码转变成一行行指令,然后解释器解释指令的执行,调用到C代码层。如果去掉指令解释这个阶段,直接进入C代码层,效率就比较高了。如果用之前所述的使用Python C API将Python代码改造为C代码并作为Python的内建模块,工作量极其大,也不能保证其正确性,所以这种方法不太现实。而Cython库正好符合这种场景需求,将已有的Python代码转化为C语言的代码,并作为Python的built-in模块扩展。

版本说明:

Python 2.7.13  (CPython)

Cython 0.25.2

Python的文件类型介绍:

.py       python的源代码文件

.pyc     Python源代码import后,编译生成的字节码

.pyo     Python源代码编译优化生成的字节码。pyo比pyc并没有优化多少,只是去掉了断言

.pyd     Python的动态链接库(Windows平台)

.py, .pyc, .pyo 运行速度几乎无差别,只是pyc, pyo文件加载的速度更快,不能用文本编辑器查看内容,反编译不太容易

 

本文的目标是将test.py文件生成test.c文件,然后将test.c文件作为Python源码的一部分,重新编译生成Python,使用时直接import test即可使用test模块。

Cython基本介绍:

文档中这样总结Cython:

Cython is an optimising static compiler for both the Python programming language and the extended Cython programming language (based on Pyrex). It makes writing C extensions for Python as easy as Python itself.

是一个Python编程语言的编译器,写C扩展就像写Python代码一样容易。

其最重要的功能是:

  • write Python code that calls back and forth from and to C or C++ code natively at any point.

即 将Python代码翻译为C代码。之后就可以像前面文章介绍的C语言扩展Python模块使用这些C代码了。

 

Cython基本用法:

 在使用Cython编译Python代码时,务必要安装C/C++编译器,本文是直接安装了Visiual Studio 2015的开发环境。

1. 安装Cython库

   pip install Cython

 就是如此简单明了

2. 编写一个测试代码文件test.py放在D:/test/test.py

def say_hello():
print "hello world"

然后在同一目录下,新建一个setup.py文件,内容如下:

from distutils.core import setup
from Cython.Build import cythonize

setup(ext_modules
= cythonize("test.py"))

cythonize()是Cython提供将Python代码转换成C代码的API,

setup是Python提供的一种发布Python模块的方法。

3. 使用命令行编译Python代码:

python setup.py build_ext  --inplace

如果出现这种情况是因为没有C编译器相关的配置没有设置好,在Windows上一般采用Microsoft VisualStudio,不同的VS版本设置不同。

  • Visual Studio 2010 (VS10): SET VS90COMNTOOLS=%VS100COMNTOOLS%
  • Visual Studio 2012 (VS11): SET VS90COMNTOOLS=%VS110COMNTOOLS%
  • Visual Studio 2013 (VS12): SET VS90COMNTOOLS=%VS120COMNTOOLS%
  • Visual Studio 2015 (VS14): SET VS90COMNTOOLS=%VS140COMNTOOLS%
  • Visual Studio 2017 (VS14): SET VS90COMNTOOLS=%VS150COMNTOOLS% 

这里采用VS2015作为C的编译器。

在命令行中输入SET VS90COMNTOOLS=%VS140COMNTOOLS%

然后输入编译命令:python setup.py build_ext --inplace

最终的生成结果如下:

在D:/test/ 目录中:

test.c是test.py转化后的C代码文件,可以看到test.c非常大!!

test.pyd是python的动态链接库,我们在使用import test时会加载

build目录编译过程中生成的临时文件

使用刚刚生成的test模块,就像使用Python的任意模块一样:

 

这里稍微解释一下 命令行:python setup.py build_ext --inplace

build_ext是指明python生成C/C++的扩展模块(build C/C++ extensions (compile/link to build directory))

--inplace指示 将编译后的扩展模块直接放在与test.py同级的目录中。

 

整个Cython工作的流程如下图所示:

分两步:

1).py文件使用Cython被编译为.c文件;

2).c文件使用C编译器生成.pyd(windos)或.so(linux)文件。

 除了这种普遍的用法外,还可以在Python代码的某些地方加上静态类型声明,也可以更进一步提升Python的运行效率,这些属于小技巧了~

比如:

def say_hello(int s):
cdef int a
= 2
print s + 2

s和a变量直接指示为int类型,不用再做动态语言的类型推断了。

 

小测试:

import math
import time

def f():
time1
= time.time()
for i in range(100000000):
x
= math.sqrt(i)
time2
= time.time()
print time2 - time1

这段原生的Python代码运行时间是13.17秒,使用Cython优化后,运行时间为9.36秒。基本上提升30%。其实Cython一般对外声称的效率提升也大概是这么多。

 

Cython中的坑

在这一小节中,讨论Cython中的一些坑以及填坑姿势。Cython官方文档中已经明确指出一些不支持的Python特性,有些不打算修复,再结合具体项目场景,给出一些坑的解决方案。

具体项目需求: 将一些需要优化的Python代码模块翻译成C代码,加入项目中,编译链接之后,作为Python的一个built-in模块。

所以,只需要转换成C代码这一步骤即可,不需要使用Python提供的distutils模块,只需要Cython提供的cythonize。

1. 从Python的site-package中提取install的Cython目录,独立出来。因为是供给其他人使用,其他人pip install cython的话可能版本不一致,会出现一些问题。

Cython目录是Cython源码以及Python2.7/Lib/site-package下的cython.py,即:

CythonTool是封装了转化为C代码的py脚本文件。

在使用时,需要设置一下sys.path,在import时才能找到我们独立出来的Cython模块。

# import Cython path
sys.path.insert(0, cython_path)
from Cython.Build import cythonize
from Cython.Compiler import Options

在sys.path的头部添加cython_path,所以Python site-package里的Cython就不会影响我们独立出来的Cython模块。

2. 在编译python代码为C代码时,需要指定输出的C代码文件路径,Cython默认的是python脚本目录,这样会导致py文件与.c文件混在一起,很容易就乱了。

目前工作目录有三个

LibDir:  需要优化的Python脚本所在目录

CfileDir: 输出的C代码文件所在的目录

ToolDir:  封装的cython优化脚本所在的目录,其作用是将LibDir中的Python模块转换为C代码,然后输出到CfileDir

故而封装的cython脚本工作目录在ToolDir,脚本中最核心的是代码是:

cythonize(pyfilePath, build_dir=CfileDir)

使用build_dir参数指明C代码输出目录。

看起来很完美,但是Cython源码在这里里有个坑。

当指定build_dir时,当pyfilePath与CfileDir都为绝对路径时,且cython脚本的工作目录与pyfilePath不一致时,cythonize会将输出文件的目录置为pyfilePath所在的目录,故最后输出的C代码文件不会到CfileDir里。

所以应该在封装的cython脚本里调用os.chdir(LibDir),转换完成时再切换到原有工作目录。牢记cython的工作目录应该与待优化的python脚本目录一致。

原因:cythonize中的实现有这样一段代码:【调试状态下】

 

红色框中,如果c_file是一个绝对文件名时,会出现以下情况,至于c_file为什么会是一个绝对的文件名,是因为cython的工作目录与待优化脚本目录不匹配导致的。

 

 3. 原始的Cython对Python的Package支持度不够,一个大坑!!

只能通过修改Cython的源码来填坑。

原始的Cython编译Python之后,生成的C代码里有两个关键的地方,拿test模块为例:

这里定义了test模块初始化函数,这个函数里会有创建test模块的代码部分:

当import时,Python解释器会调用这里,初始化test模块,将test名字加入到sys.builtin_module_names中。

测试发现,如果有D:/Lib/mypackate/test.py , 编译后,生成的C代码与D:/Lib/test.py生成的代码并无不同,即mypackate这个包被忽略了,导致生成的C代码没有了包依赖关系。

顺着代码阅读,最终确定了问题出现的源头,Cython/Compiler/ModuleNode.py, 修改了此文件中的两个函数:

1)生成模块init代码函数:full_module_name替换掉env.module_name, 即用initmypackage_test替换init_test

2) 修改了创建模块时传入的模块名规则,并考虑到mypackage/__init__.py这种情况, 对于package来说需要加入__path__用以标识这个对象不是普通的Python模块,而是一个包。

 

 4.  深坑。 inspect、types相关。

Inspect模块中有各种类型判断函数,比如 isfunction, ismethod, ismodule等。这里的坑是:

cython化的函数类型变为了cython_function_or_method,而原始python的函数类型是function,所以如果待优化的Python脚本中使用isfunction(func, types.FunctionType)时,如果func是原始的函数则返回True,而cython化的函数返回False. 除了function类型外还有generator, functionType.func_globals类型也存在不一致。

目前在inspect.py的isfunction中加入了trick,会判断

type(func).__name__=="cython_function_or_method". 并且types.py模块不被cython化,那么如果调用inspect.isfunction(func, types.FunctionType)对于原始的Python函数还是cython化的函数都没有问题了。

但是如果直接使用isinstance(func, types.FunctionType)仍然会存在问题,types.FunctionType只对原始的python函数判断正确。

比较绕,总而言之一句话,python里的类型和cython化后的对应的类型可能会不同。我总结了大部分python类型,其中有几个cython化后类型不一致:

没有什么太好的解决办法,要么改写inspect模块,但还要保证Python代码不能直接使用types模块,要么修改Python源码中关于isinstance的实现。

5. 官方文档中列出的坑

1) 不支持Nested tuple, Python2中的特性,Python3不支持了。所以Cython直接不支持Nested tuple特性

2)找不到变量名:You can disable the latter behaviour by setting "error_on_unknown_names" to

 解决办法:

3)Stack Frames. 

 Cython不支持Stack Frame。

 

总结:可以考虑使用Cython优化一些简单的Python项目,如果用到非常复杂的场景的话,有些语法的特性不支持,会有绕不过去的坑

 

参考资料:

https://github.com/cython/cython

https://mdqinc.com/blog/2011/08/statically-linking-python-with-cython-generated-modules-and-packages/

 


推荐阅读
  • 恶意软件分析的最佳编程语言及其应用
    本文介绍了学习恶意软件分析和逆向工程领域时最适合的编程语言,并重点讨论了Python的优点。Python是一种解释型、多用途的语言,具有可读性高、可快速开发、易于学习的特点。作者分享了在本地恶意软件分析中使用Python的经验,包括快速复制恶意软件组件以更好地理解其工作。此外,作者还提到了Python的跨平台优势,使得在不同操作系统上运行代码变得更加方便。 ... [详细]
  • 本文比较了eBPF和WebAssembly作为云原生VM的特点和应用领域。eBPF作为运行在Linux内核中的轻量级代码执行沙箱,适用于网络或安全相关的任务;而WebAssembly作为图灵完备的语言,在商业应用中具有优势。同时,介绍了WebAssembly在Linux内核中运行的尝试以及基于LLVM的云原生WebAssembly编译器WasmEdge Runtime的案例,展示了WebAssembly作为原生应用程序的潜力。 ... [详细]
  • 如何用UE4制作2D游戏文档——计算篇
    篇首语:本文由编程笔记#小编为大家整理,主要介绍了如何用UE4制作2D游戏文档——计算篇相关的知识,希望对你有一定的参考价值。 ... [详细]
  • 本文介绍了Hyperledger Fabric外部链码构建与运行的相关知识,包括在Hyperledger Fabric 2.0版本之前链码构建和运行的困难性,外部构建模式的实现原理以及外部构建和运行API的使用方法。通过本文的介绍,读者可以了解到如何利用外部构建和运行的方式来实现链码的构建和运行,并且不再受限于特定的语言和部署环境。 ... [详细]
  • 如何用JNI技术调用Java接口以及提高Java性能的详解
    本文介绍了如何使用JNI技术调用Java接口,并详细解析了如何通过JNI技术提高Java的性能。同时还讨论了JNI调用Java的private方法、Java开发中使用JNI技术的情况以及使用Java的JNI技术调用C++时的运行效率问题。文章还介绍了JNIEnv类型的使用方法,包括创建Java对象、调用Java对象的方法、获取Java对象的属性等操作。 ... [详细]
  • 全面介绍Windows内存管理机制及C++内存分配实例(四):内存映射文件
    本文旨在全面介绍Windows内存管理机制及C++内存分配实例中的内存映射文件。通过对内存映射文件的使用场合和与虚拟内存的区别进行解析,帮助读者更好地理解操作系统的内存管理机制。同时,本文还提供了相关章节的链接,方便读者深入学习Windows内存管理及C++内存分配实例的其他内容。 ... [详细]
  • 本文介绍了lua语言中闭包的特性及其在模式匹配、日期处理、编译和模块化等方面的应用。lua中的闭包是严格遵循词法定界的第一类值,函数可以作为变量自由传递,也可以作为参数传递给其他函数。这些特性使得lua语言具有极大的灵活性,为程序开发带来了便利。 ... [详细]
  • Android Studio Bumblebee | 2021.1.1(大黄蜂版本使用介绍)
    本文介绍了Android Studio Bumblebee | 2021.1.1(大黄蜂版本)的使用方法和相关知识,包括Gradle的介绍、设备管理器的配置、无线调试、新版本问题等内容。同时还提供了更新版本的下载地址和启动页面截图。 ... [详细]
  • Voicewo在线语音识别转换jQuery插件的特点和示例
    本文介绍了一款名为Voicewo的在线语音识别转换jQuery插件,该插件具有快速、架构、风格、扩展和兼容等特点,适合在互联网应用中使用。同时还提供了一个快速示例供开发人员参考。 ... [详细]
  • 计算机存储系统的层次结构及其优势
    本文介绍了计算机存储系统的层次结构,包括高速缓存、主存储器和辅助存储器三个层次。通过分层存储数据可以提高程序的执行效率。计算机存储系统的层次结构将各种不同存储容量、存取速度和价格的存储器有机组合成整体,形成可寻址存储空间比主存储器空间大得多的存储整体。由于辅助存储器容量大、价格低,使得整体存储系统的平均价格降低。同时,高速缓存的存取速度可以和CPU的工作速度相匹配,进一步提高程序执行效率。 ... [详细]
  • Google Play推出全新的应用内评价API,帮助开发者获取更多优质用户反馈。用户每天在Google Play上发表数百万条评论,这有助于开发者了解用户喜好和改进需求。开发者可以选择在适当的时间请求用户撰写评论,以获得全面而有用的反馈。全新应用内评价功能让用户无需返回应用详情页面即可发表评论,提升用户体验。 ... [详细]
  • 本文介绍了计算机网络的定义和通信流程,包括客户端编译文件、二进制转换、三层路由设备等。同时,还介绍了计算机网络中常用的关键词,如MAC地址和IP地址。 ... [详细]
  • Android源码深入理解JNI技术的概述和应用
    本文介绍了Android源码中的JNI技术,包括概述和应用。JNI是Java Native Interface的缩写,是一种技术,可以实现Java程序调用Native语言写的函数,以及Native程序调用Java层的函数。在Android平台上,JNI充当了连接Java世界和Native世界的桥梁。本文通过分析Android源码中的相关文件和位置,深入探讨了JNI技术在Android开发中的重要性和应用场景。 ... [详细]
  • Java在运行已编译完成的类时,是通过java虚拟机来装载和执行的,java虚拟机通过操作系统命令JAVA_HOMEbinjava–option来启 ... [详细]
  • 树莓派语音控制的配置方法和步骤
    本文介绍了在树莓派上实现语音控制的配置方法和步骤。首先感谢博主Eoman的帮助,文章参考了他的内容。树莓派的配置需要通过sudo raspi-config进行,然后使用Eoman的控制方法,即安装wiringPi库并编写控制引脚的脚本。具体的安装步骤和脚本编写方法在文章中详细介绍。 ... [详细]
author-avatar
手机用户2502908277
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有