Python优化:编译优化篇(Cpython、Numba)

概述

python为通过编译优化代码效率的方式提供了许多选项,包括纯粹的基于C的编译方式,比如Cython、 Shed Skin和 Pythran凭借 Numba 的基于 LLVM 的编译方式,还有替代虚拟机的 PyPy,包含了一个内置的即时编译器(JIT)。

面向的提升对象

调用外部库(例如,正则表达式、字符串操作、调用数据库)的代码在编译后可能不会表现出任何速度提升。 I/O 密集型的程序同样不可能表现出明显的速度提升。因为这是机理问题
类似地,如果你 Python 代码集中于调用向量化的 numpy ,那么在编译后就不大可能运行得更快。只有当被编译的代码主要是 Python(并且可能主要是循环)时才会运行得更快。

总结就是我们优化的主要目标是:循环

Pytho编译工具

  • 提前编译工具( Cython、 Shed Skin、 Pythran)

  • “即时”编译工具( Numba、 PyPy)

区别:通过提前编译( AOT),会创建一个为你的机器定制的静态库。而对于即时编译,编译器在使用时只逐步编译用的部分代码,但这不适合处理短小频繁运行的脚本。

Cpython

Cython 是一个能把类型注解的 Python 转换为一个扩展编译模块的编译器。
Cpython编译优化案例

  • 首先编写三个文件
1、本程序为主测试(调用cpython优化模块)的文件。
-------------------------
# main.py
import time

import calculate # 与Cpython编译过程中setup.py里面定义的一致

zs = [i for i in range(10000)]
cs = [x**2 for x in range(10000)]

def calculate_z(maxiter, zs, cs):
    """Calculate output list using Julia update rule"""
    output = [0] * len(zs)
    for i in range(len(zs)):
        n = 0
        z = zs[i]
        c = cs[i]
        while n < maxiter and abs(z) < 2:
            z = z * z + c
            n += 1
        output[i] = n
    return output

def calc_pure_python(desired_width, max_iterations,zs,cs):
    start_time = time.time()
    output = calculate.calculate_z(max_iterations, zs, cs)
    # output = calculate_z(max_iterations, zs, cs) Took 1.5417897701263428 seconds
    end_time = time.time()
    secs = end_time - start_time
    print("Took", secs, "seconds")


calc_pure_python(500,10000000,zs,cs)

-------------------------
2、cpythonfunc.pyx是我们准备编译的CPU密集计算型函数;例如:
-------------------------
# cythonfn.pyx
def calculate_z(maxiter, zs, cs):
    """Calculate output list using Julia update rule"""
    output = [0] * len(zs)
    for i in range(len(zs)):
        n = 0
        z = zs[i]
        c = cs[i]
        while n < maxiter and abs(z) < 2:
            z = z * z + c
            n += 1
        output[i] = n
    return output
--------------------------
3、setup.py文件,定义了把 cythonfn.pyx (自己定义的名)转换为 calculate.so(自己定义的模块名) 的方法
from distutils.core import setup
from distutils.extension import Extension
from Cython.Distutils import build_ext
setup(
cmdclass = {'build_ext': build_ext},
ext_modules = [Extension("calculate", ["cpythonfunc.pyx"])]
)
  • 然后运行 setup.py 获得一个能够被导入的模块:
python setup.py build_ext --inplace
'''--inplace 参数让 Cython 在当前目录中构建编译模块,而不是在一个独立的构建
目录中。'''

注意:另外我们还可以在 cythonfn.pyx 中利用Cdef关键字在函数体内声明变量(函数顶部),因为增加原始C类型可以使得编译函数运行更快。

def calculate_z(int maxiter, zs, cs):
	"""Calculate output list using Julia update rule"""
	cdef unsigned int i, n
	cdef double complex z, c
	output = [0] * len(zs)
	...

总结:CPython 的优化注意两部分:函数模块编译;加入Cdef类型可以使得优化更快。

Shed Skin

Shed Skin 是一个和 Python2.4~ 2.7 一起使用的实验性的把 Python 转为 C++的编译器。
实质上, Shed Skin 和 Cython 生成了相同的机器码,执行速度的差异归结于 Shed Skin 在一个独立的内存空间运行,还有就是
所需的拷进拷出数据的开销。硬币的另一面就是,使用 Shed Skin,你不必预先做注解工作,节省了相当多的时间。
总结就是:Shed Skin不一定比CPython快,是
所需的拷进拷出数据的开销。

并行化

与 Cython,Numba/pro,PyPy 一起,OpenMP 能够通过使用 prange(并行 range)操作符和给 setup.py增加-fopenmp 编译指令的方式加入进来。在一个 prange 循环中的工作就能够做到并行运行,因为我们禁止了全局解释器锁( GIL)。

Numba

Numba 是一个专用于 numpy 代码的即时编译器, 在运行时由 LLVM 编译器来编译。
怎么使用

from numba import jit
...
@jit()
def calculate_serial_purepython(*args,**kwargs)

可以看到,用法非常简单,导入包,在需要优化的计算型函数前面添加修饰器@jit(),其优化效果和cpython差不多。
问题在于:Numba第一次显得慢,之后再调用函数就快了。因为第一次编译过后,参数没变的话就继续用,就快了。

NumbaPro

Numba 的升级版本,更适合硬件,更快。

PyPy

PyPy 是一个 Python 语言的替代实现,包含了一个可跟踪的即时编译器。
PyPy能达到比CPython更快的性能。注意:PyPy不能处理numpy,特别慢。

回顾总结

一般情况下,可能最消耗 CPU 时间的代码行是下面这些:
• 在紧凑的内循环内。
• 解引用 list、 array 或者 np.array 这些项。
• 执行数学运算。

能用到的加速包:
Cpython、Numba/pro、Pypy、Openmp

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值