楔子
后面咱们说了 Cython 是什么,为什么咱们要用它,以及如何编译和运行 Cython 代码。有了这些常识,那么是时候进入 Cython 的深度摸索之路了。不过在此之前,咱们还是要深入分析一下 Python 和 Cython 的区别。
Python 和 Cython 的差异从大方向上来说无非有两个,一个是:运行时解释和事后编译;另一个是:动静类型和动态类型。
解释执行和编译执行
为了更好地了解为什么 Cython 能够进步 Python 代码的执行性能,有必要比照一下虚拟机执行 Python 代码和操作系统执行曾经编译好的 C 代码之间的差异。
Python 代码在运行之前,会先被编译成 pyc 文件(外面存储的是 PyCodeObject 对象),而后读取外面的 PyCodeObject 对象,创立栈帧,执行外部的字节码。而字节码是可能被 Python 虚拟机解释或者执行的根底指令集,并且虚拟机独立于平台,因而在一个平台生成的字节码能够在任意平台运行。
虚拟机将一个高级字节码翻译成一个或者多个能够被操作系统调度 CPU 执行的低级操作(指令)。这种虚拟化很常见并且非常灵便,能够带来很多益处:其中一个益处就是不会被挑剔的操作系统厌弃(相较于编译型语言,你在一个平台编译的可执行文件在其它平台上可能就用不了了),而毛病是运行速度比本地编译好的代码慢。
站在 C 的角度,因为不存在虚拟机,因而也就不存在所谓的高级字节码。C 代码会被间接编译成机器码,以一个可执行文件或者动静库(.dll 或者 .so)的模式存在。然而留神:它依赖于以后的操作系统,是为以后平台和架构量身打造的,能够间接被 CPU 执行,而且级别非常低(随同着速度快),所以它与所在的操作系统是有关系的。
那么有没有一种方法能够补救虚拟机的字节码和 CPU 的机器码之间的宏观差别呢?
答案是有的,那就是 C 代码能够被编译成一种被称之为扩大模块的特定类型的动静库,并且这些库能够作为成熟的 Python 模块,然而外面的内容曾经是经由规范 C 编译器编译成的机器码。Python 虚拟机在导入扩大模块执行的时候,不会再解释高级字节码,而是间接运行机器代码,这样就能移除性能开销。
这里再提一下扩大模块,咱们说 Windows 中存在 .dll(动态链接库)、Linux 中存在 .so(共享文件)。如果只是 C 或者 C++、甚至是 Go 等等编写的一般源文件,而后编译成 .dll 或者 .so,那么这两者能够通过 ctypes 调用,然而无奈通过 import 导入。如果你强行导入,那么会报错:
ImportError: dynamic module does not define module export function
但如果是遵循 Python/C API 编写,只管编译出的扩大模块在 Linux 上也是 .so、Windows 上是 .pyd(.pyd 也是个 .dll),但它们是能够间接被解释器辨认被导入的。
将一个一般的 Python 代码编译成扩大模块的话(Cython 是 Python 的超集,即便是纯 Python 也能够编译成扩大模块),效率上能够有多大的晋升呢?依据 Python 代码所做的事件,这个差别会十分宽泛,然而通常将 Python 代码转换成等效的扩大模块的话,效率大略有 10% 到 30% 的晋升。因为个别状况下,代码既有 IO 密集也会有 CPU 密集。
所以即使没有任何的 Cython 代码,纯 Python 在编译成扩大模块之后也会有性能的晋升。并且如果代码是计算密集型,那么效率会更高。
Cython 给了咱们收费减速的便当,让咱们在不写 Cython、也就是只写纯 Python 的状况下,还能失去优化。但这种只针对纯 Python 进行的优化显然只是扩大模块的冰山一角,真正的性能改良是应用 Cython 的动态类型来替换 Python 的动静解析。
因为 Python 不会进行基于类型的优化,所以即便编译成扩大模块,但如果类型不确定,还是没有方法达到高效率的。
就拿两个变量相加举例:因为 Python 不会做基于类型方面的优化,所以这一行代码对应的机器码数量显然会很多,即便编译成了扩大模块,其对应的机器码数量也是相似的(外部会有优化,因而机器码数量可能会少一些,但不会少太多)。
这两者区别就是:一般的模块有一个翻译的过程,将字节码翻译成机器码;而扩大模块是当时就曾经全副翻译成机器码了。然而 CPU 执行的时候,因为机器码数量是差不多的,因而执行工夫也是差不多的,区别就是少了一个翻译的过程。然而很显著,Python 将字节码翻译成机器码破费的工夫简直是不须要思考的,重点是 CPU 在执行机器码所破费的工夫。
因而将纯 Python 代码编译成扩大模块,速度不会晋升太显著,晋升的 10~30% 也是 Cython 编译器外部的优化,比方:发现函数中某个对象在函数完结就不被应用了,所以将其调配的栈上等等。如果应用 Cython 时指定了类型,那么因为类型确定,机器码的数量就会大幅度缩小。CPU 执行 10 条机器码花的工夫和执行 1 条机器码花的工夫哪个长,显而易见。
因而应用 Cython,重点是规定好类型,一旦类型确定,那么速度会快很多。
动静类型和动态类型
Python 语言和 C、C++ 之间的另一个重要的差别就是:前者是动静语言,后者是动态语言。动态语言要求在编译的时候就必须确定变量的类型,个别通过显式的申明来实现这一点。另一方面,如果一旦申明某个变量,那么之后此作用域中该变量的类型就不能够再扭转了。
看起来限度还蛮多的,那么动态类型能够带来什么益处呢?除了编译时的类型检测,编译器也能够依据动态类型生成适应以后平台的高性能机器码。
动静语言(针对于 Python)则不一样,对于动静语言来说,类型不是和变量绑定的,而是和对象绑定的,变量只是一个指向对象的指针罢了。因而 Python 中如果想创立一个变量,那么必须在创立的同时赋上值,不然解释器不晓得这个变量到底指向哪一个对象。而像 C 这种动态语言,能够创立一个变量的同时不赋上初始值,比方:int n,因为曾经晓得 n 是一个 int 类型了,所以调配的空间大小曾经确定了。
并且对于动静语言来说,变量即便在同一个作用域中,也能够指向任意的对象,因为变量只是一个指针罢了。举个栗子:
var = 666
var = "python 编程学习圈"
首先是 var = 666,相当于创立了一个整数 666,而后让 var 这个变量指向它;
再来一个 var = “python 编程学习圈 ”,那么会创立一个字符串,而后让 var 指向这个字符串。或者说 var 不再存储整数 666 的地址,而是存储新创建的字符串的地址。
所以在运行 Python 程序时,解释器要花费很多工夫来确认执行的低阶操作,并抽取相应的数据。思考到 Python 设计的灵活性,解释器总是要以一种十分通用的形式来确定相应的低阶操作,因为 Python 的变量在任意时刻能够指向任意类型的数据。以上便是所谓的动静解析,而 Python 的通用动静解析是迟缓的,还是以 a + b 为栗:
*1)解释器要检测 a 指向的对象的类型,这在 C 一级至多须要一次指针查找;
2)解释器从该类型中寻找加法办法的实现,这可能又须要一个或者多个额定的指针查找和外部函数调用;
3)如果解释器找到了相应的办法,那么解释器就有了一个理论的函数调用;
4)解释器会调用这个加法函数,并将 a 和 b 作为参数传递进去;
5)Python 的对象在 C 中都是一个构造体,比方:整数在 C 中是 PyLongObject,外部有援用计数、类型、ob_size、ob_digit,这些成员是什么不用关怀,总之其中一个成员必定是寄存具体的值的,其它成员则是存储额定的属性的。
而加法函数显然要从这两个构造体中抽出理论的数据,这须要指针查找以及将数据从 Python 类型转换到 C 类型。如果胜利,那么会执行加法的实际操作;如果不胜利,比方类型不对,发现 a 是整数但 b 是个字符串,就会报错;
6)执行完加法操作之后,必须将后果再转回 Python 对象,而后获取它的指针、转成 PyObject 之后再返回;
以上就是 Python 执行 a + b 的流程,而 C 语言面对 a + b 这种状况,体现则是不同的。因为 C 是动态编译型语言,C 编译器在编译的时候就决定了执行的低阶操作和要传递的参数数据。
在运行时,一个编译好的 C 程序简直跳过了 Python 解释器要必须执行的所有步骤。对于 a + b,编译器提前就确定好了类型,比方整型,那么编译器生成的机器码指令是寥寥可数的:将数据加载至寄存器进行相加,而后存储后果。
所以咱们看到编译后的 C 程序简直将所有的工夫都只花在了调用疾速的 C 函数以及执行基本操作上,没有 Python 那些花里胡哨的动作。并且因为动态语言对变量类型的限度,编译器会生成更疾速、更业余的指令,这些指令是为其数据以及所在平台量身打造的。因而 C 语言比 Python 快上几十倍甚至上百倍,这几乎再失常不过了。
而 Cython 在性能上能够带来如此微小的晋升的起因就在于,它将 C 的动态类型引入到 Python 中,动态类型会将运行时的动静解析转化成基于类型优化的机器码。
在 Cython 诞生之前,咱们只能通过 C 从新实现 Python 代码来从动态类型中获益,也就是用 C 编写所谓的扩大模块。但 Cython 的呈现则简化了这一点,能够让咱们在写相似于 Python 代码的同时,还能应用 C 的动态类型零碎。
以上就是本次分享的所有内容,想要理解更多 python 常识欢送返回公众号:Python 编程学习圈 ,发送“J”即可收费获取,每日干货分享