Cython基础教程(一) - 基本概念介绍

最近工作中的一些项目需要用到cython做加速,但网上都是一些零散的教程,唯一参考的文献是官方的文档和OReilly的书,但都是英文的,对于英文不好的同学可能阅读起来比较吃力。所以我个人花了一些时间,根据参考文献,系统的梳理了一下cython的用法,希望能够帮助到更多的同学。

简介

Cython是一种拓展的python, 融合了静态类型的c/c++, 其文件扩展名为.pyx, 这种类型的文件经过编译之后可以变成供python直接调用的动态链接库(.so). Cython程序需要先编译之后才能被python调用,流程是:

  • Cython编译器把Cython代码编译成C/C++代码
  • 把生成的代码编译成动态链接库
  • python解释器载入动态链接库

Cython的作用在于它结合了python和c的优点。 python是高级语言,动态类型,很灵活,解释性语言,但是相比与静态类型的编译语言,速度慢好几个数量级。python代码在运行之前会转换为python字节码,通过python虚拟机将高级的字节码转化为低级别的操作,可以被cpu执行。这样做的好处是python代码可以不需要编译运行,但缺点就是相比于编译好的代码,运行速度比较慢。
C语言是广泛使用的静态编译语言,速度很快,但是相比与高级语言很难使用。C代码必须经过编译之后才可与被cpu直接运行,但是运行效率更高

为了加速python程序, Cython将python的一些变量定义为静态类型实现加速。
我们可以将cython的源代码编译成一个扩展模块让python调用。

python, cython代码比较

def fib(n):
    a, b = 0, 1
    for i in range(n):
        a, b = a + b, a
    return a
def fib(int n):
    cdef int i
    cdef int a=0, b=1
    for i in range(n):
        a, b = a + b, a
    return a

纯python的代码也是有效的cython代码,可以通过cython编译,但是纯python代码经过cython编译中性能提升不大。
我们使用cdef来声明静态类型变量。因为python是动态类型的编程语言,所以不能进行基于类型的优化。类似与a+b的表达式,在python中,需要要先推断a和b的类型,找到该类型下的__add__方法,然后在调用。而cython代码定义了变量的类型,因此不需要做类型检查,所以速度会更快

大家可以动手尝试去跑一跑看看性能的提升。在此之前先看看cython代码如何编译

编译和运行cython代码

首先需要配置cython环境

$pip install cython
$cython -V

python的标准库中包含distutils packages, 用于打包python的项目, 它们可以将编译好的c代码打包成python的一个扩展模块。来看一个实际例子。
1 . 新建一个fib.pyx文件,包含如下代码:

def fib(int n):
    cdef int i
    cdef int a=0, b=1
    for i in range(n):
        a, b = a + b, a
    return a

2 . 新建一个setup.py文件

from distutils.core import setup, Extension
from Cython.Build import cythonize
# name表示扩展模块的名称
ext = Extension(name='fib', source=['fib.pyx'])
setup(ext_modules=cythonize(ext))

cythonize()函数调用cython编译器编译.pyx源文件,setup()函数将编译好的c/c++代码打包成python拓展模块

3 . 进行编译

$ python setup.py build_ext --inplace

build_ext参数代表要构建一个扩展模块,–inplace参数代表将扩展模块放在.pyx文件夹下
编译好之后,我们可以看到在.pyx文件夹有一个.so文件,这个.so文件可以被python调用,我们通过如下代码观察cython的加速

import time
from functools import wraps
# 导入cython生成的.so文件
import fib


def time_wrapper(func):
  @wraps(func)
  def measure_time(*args, **kwargs):
    t1 = time.time()
    result = func(*args, **kwargs)
    t2 = time.time()
    print(f"{func.__name__} took {t2 - t1} seconds")
    return result
  return measure_time

@time_wrapper
def fib_py(n):
  a = 0
  b = 0
  for _ in range(n):
    a, b = a + b, a
  return a

@time_wrapper
def fib_c(n):
  # 调用.so
  return fib.fib(n)

if __name__ == "__main__":
  n = 10000
  fib_py(n)
  fib_c(n)

"""
fib_py took 0.00047588348388671875 seconds
fib_c took 3.814697265625e-06 seconds
"""

可以看出,我们仅仅在pyx中声明了变量类型,就取得了几百倍的加速效果

使用cython包装c代码

cython一个主要特点是可以与外部的代码进行交互,包括python,c/c++代码。 因为cython可以理解c/c++的变量声明,可以与外部的库进行交互,所以可以生成高度优化的代码。看个例子:

1 . 新建头文件cfib.h

#ifndef __CFIB_H_
#define __CFIB_H_

double cfib(int n);
#endif

2 . 新建cfib.c完成函数体的定义

# include "cfib.h"

double cfib(int n ) {
    int i ;
    double a = 0.0, b = 1.0, tmp;
    for (i=0; i<n; ++i) {
        tmp = a; a = a + b; b = tmp;
    }
    return a
}

3 . 编译cython代码wrap_fib.pyx

cdef extern from "cfib.h":
    double cfib(int n)

# 对外的接口
def fib(n):
    return cfib(n)

4 . 编写setup.py

from distutils.core import setup, Extension
from Cython.Build import cythonize
# name表示扩展模块的名称
ext = Extension(name='wrap_fib', source=['cfib.c', 'wrap_fib.pyx'])
setup(ext_modules=cythonize(ext))

同样的,在terminal运行如下的命令来生成.so文件, 可供python进行调用

# python setup.py build_ext --inplace

大家可以按照上述步骤进行实践,速度应该会提高狠多。

值得注意的是,当我们要提高python代码性能的时候,根据二八定律,80%的运行时间都是有20%的代码造成的,没有必要去优化所有的python代码,至于如何找到那20%的代码,后续的教程会介绍

  • 20
    点赞
  • 70
    收藏
    觉得还不错? 一键收藏
  • 4
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值