Python Cookbook(第3版)中文版:15.11 用Cython写高性能的数组操作

15.11 用Cython写高性能的数组操作?

问题?

你要写高性能的操作来自NumPy之类的数组计算函数。
你已经知道了Cython这样的工具会让它变得简单,但是并不确定该怎样去做。

解决方案?

作为一个例子,下面的代码演示了一个Cython函数,用来修整一个简单的一维双精度浮点数数组中元素的值。

# sample.pyx (Cython)

cimport cython

@cython.boundscheck(False)
@cython.wraparound(False)
cpdef clip(double[:] a, double min, double max, double[:] out):
    ‘‘‘
    Clip the values in a to be between min and max. Result in out
    ‘‘‘
    if min > max:
        raise ValueError("min must be <= max")
    if a.shape[0] != out.shape[0]:
        raise ValueError("input and output arrays must be the same size")
    for i in range(a.shape[0]):
        if a[i] < min:
            out[i] = min
        elif a[i] > max:
            out[i] = max
        else:
            out[i] = a[i]

要编译和构建这个扩展,你需要一个像下面这样的 setup.py 文件
(使用 python3 setup.py build_ext --inplace 来构建它):

from distutils.core import setup
from distutils.extension import Extension
from Cython.Distutils import build_ext

ext_modules = [
    Extension(‘sample‘,
              [‘sample.pyx‘])
]

setup(
  name = ‘Sample app‘,
  cmdclass = {‘build_ext‘: build_ext},
  ext_modules = ext_modules
)

你会发现结果函数确实对数组进行的修正,并且可以适用于多种类型的数组对象。例如:

>>> # array module example
>>> import sample
>>> import array
>>> a = array.array(‘d‘,[1,-3,4,7,2,0])
>>> a

array(‘d‘, [1.0, -3.0, 4.0, 7.0, 2.0, 0.0])
>>> sample.clip(a,1,4,a)
>>> a
array(‘d‘, [1.0, 1.0, 4.0, 4.0, 2.0, 1.0])

>>> # numpy example
>>> import numpy
>>> b = numpy.random.uniform(-10,10,size=1000000)
>>> b
array([-9.55546017,  7.45599334,  0.69248932, ...,  0.69583148,
       -3.86290931,  2.37266888])
>>> c = numpy.zeros_like(b)
>>> c
array([ 0.,  0.,  0., ...,  0.,  0.,  0.])
>>> sample.clip(b,-5,5,c)
>>> c
array([-5.        ,  5.        ,  0.69248932, ...,  0.69583148,
       -3.86290931,  2.37266888])
>>> min(c)
-5.0
>>> max(c)
5.0
>>>

你还会发现运行生成结果非常的快。
下面我们将本例和numpy中的已存在的 clip() 函数做一个性能对比:

>>> timeit(‘numpy.clip(b,-5,5,c)‘,‘from __main__ import b,c,numpy‘,number=1000)
8.093049556000551
>>> timeit(‘sample.clip(b,-5,5,c)‘,‘from __main__ import b,c,sample‘,
...         number=1000)
3.760528204000366
>>>

正如你看到的,它要快很多——这是一个很有趣的结果,因为NumPy版本的核心代码还是用C语言写的。

讨论?

本节利用了Cython类型的内存视图,极大的简化了数组的操作。
cpdef clip() 声明了 clip() 同时为C级别函数以及Python级别函数。
在Cython中,这个是很重要的,因为它表示此函数调用要比其他Cython函数更加高效
(比如你想在另外一个不同的Cython函数中调用clip())。

类型参数 double[:] a 和 double[:] out 声明这些参数为一维的双精度数组。
作为输入,它们会访问任何实现了内存视图接口的数组对象,这个在PEP 3118有详细定义。
包括了NumPy中的数组和内置的array库。

当你编写生成结果为数组的代码时,你应该遵循上面示例那样设置一个输出参数。
它会将创建输出数组的责任给调用者,不需要知道你操作的数组的具体细节
(它仅仅假设数组已经准备好了,只需要做一些小的检查比如确保数组大小是正确的)。
在像NumPy之类的库中,使用 numpy.zeros() 或 numpy.zeros_like()
创建输出数组相对而言比较容易。另外,要创建未初始化数组,
你可以使用 numpy.empty() 或 numpy.empty_like() .
如果你想覆盖数组内容作为结果的话选择这两个会比较快点。

你你的函数实现中,你只需要简单的通过下标运算和数组查找(比如a[i],out[i]等)来编写代码操作数组。
Cython会负责为你生成高效的代码。

clip() 定义之前的两个装饰器可以优化下性能。
@cython.boundscheck(False) 省去了所有的数组越界检查,
当你知道下标访问不会越界的时候可以使用它。
@cython.wraparound(False) 消除了相对数组尾部的负数下标的处理(类似Python列表)。
引入这两个装饰器可以极大的提升性能(测试这个例子的时候大概快了2.5倍)。

任何时候处理数组时,研究并改善底层算法同样可以极大的提示性能。
例如,考虑对 clip() 函数的如下修正,使用条件表达式:

@cython.boundscheck(False)
@cython.wraparound(False)
cpdef clip(double[:] a, double min, double max, double[:] out):
    if min > max:
        raise ValueError("min must be <= max")
    if a.shape[0] != out.shape[0]:
        raise ValueError("input and output arrays must be the same size")
    for i in range(a.shape[0]):
        out[i] = (a[i] if a[i] < max else max) if a[i] > min else min

实际测试结果是,这个版本的代码运行速度要快50%以上(2.44秒对比之前使用 timeit() 测试的3.76秒)。

到这里为止,你可能想知道这种代码怎么能跟手写C语言PK呢?
例如,你可能写了如下的C函数并使用前面几节的技术来手写扩展:

void clip(double *a, int n, double min, double max, double *out) {
  double x;
  for (; n >= 0; n--, a++, out++) {
    x = *a;

    *out = x > max ? max : (x < min ? min : x);
  }
}

我们没有展示这个的扩展代码,但是试验之后,我们发现一个手写C扩展要比使用Cython版本的慢了大概10%。
最底下的一行比你想象的运行的快很多。

你可以对实例代码构建多个扩展。
对于某些数组操作,最好要释放GIL,这样多个线程能并行运行。
要这样做的话,需要修改代码,使用 with nogil: 语句:

@cython.boundscheck(False)
@cython.wraparound(False)
cpdef clip(double[:] a, double min, double max, double[:] out):
    if min > max:
        raise ValueError("min must be <= max")
    if a.shape[0] != out.shape[0]:
        raise ValueError("input and output arrays must be the same size")
    with nogil:
        for i in range(a.shape[0]):
            out[i] = (a[i] if a[i] < max else max) if a[i] > min else min

如果你想写一个操作二维数组的版本,下面是可以参考下:

@cython.boundscheck(False)
@cython.wraparound(False)
cpdef clip2d(double[:,:] a, double min, double max, double[:,:] out):
    if min > max:
        raise ValueError("min must be <= max")
    for n in range(a.ndim):
        if a.shape[n] != out.shape[n]:
            raise TypeError("a and out have different shapes")
    for i in range(a.shape[0]):
        for j in range(a.shape[1]):
            if a[i,j] < min:
                out[i,j] = min
            elif a[i,j] > max:
                out[i,j] = max
            else:
                out[i,j] = a[i,j]

希望读者不要忘了本节所有代码都不会绑定到某个特定数组库(比如NumPy)上面。
这样代码就更有灵活性。
不过,要注意的是如果处理数组要涉及到多维数组、切片、偏移和其他因素的时候情况会变得复杂起来。
这些内容已经超出本节范围,更多信息请参考 PEP 3118 ,
同时 Cython文档中关于“类型内存视图”
篇也值得一读。

艾伯特(http://www.aibbt.com/)国内第一家人工智能门户

原文地址:https://www.cnblogs.com/5rjscn/p/8542840.html

时间: 2024-10-30 15:46:59

Python Cookbook(第3版)中文版:15.11 用Cython写高性能的数组操作的相关文章

Python Cookbook(第3版) 中文版 pdf完整版高清下载

Python Cookbook(第3版)中文版介绍了Python应用在各个领域中的一些使用技巧和方法,其主题涵盖了数据结构和算法,字符串和文本,数字.日期和时间,迭代器和生成器,文件和I/O,数据编码与处理,函数,类与对象,元编程,模块和包,网络和Web编程,并发,实用脚本和系统管理,测试.调试以及异常,C语言扩展等. 本书覆盖了Python应用中的很多常见问题,并提出了通用的解决方案.书中包含了大量实用的编程技巧和示例代码,并在Python 3.3环境下进行了测试,可以很方便地应用到实际项目中

Python Cookbook(第3版)中文版:14.11 输出警告信息

14.11 输出警告信息? 问题? 你希望自己的程序能生成警告信息(比如废弃特性或使用问题). 解决方案? 要输出一个警告消息,可使用 warning.warn() 函数.例如: import warnings def func(x, y, logfile=None, debug=False): if logfile is not None: warnings.warn('logfile argument deprecated', DeprecationWarning) ... warn() 的

Python Cookbook(第3版)中文版:15.13 传递NULL结尾的字符串给C函数库

15.13 传递NULL结尾的字符串给C函数库? 问题? 你要写一个扩展模块,需要传递一个NULL结尾的字符串给C函数库.不过,你不是很确定怎样使用Python的Unicode字符串去实现它. 解决方案? 许多C函数库包含一些操作NULL结尾的字符串,被声明类型为 char * .考虑如下的C函数,我们用来做演示和测试用的: void print_chars(char *s) { while (*s) { printf("%2x ", (unsigned char) *s); s++;

Python Cookbook(第3版)中文版:15.1 使用ctypes访问C代码

15.1 使用ctypes访问C代码? 问题? 你有一些C函数已经被编译到共享库或DLL中.你希望可以使用纯Python代码调用这些函数,而不用编写额外的C代码或使用第三方扩展工具. 解决方案? 对于需要调用C代码的一些小的问题,通常使用Python标准库中的 ctypes 模块就足够了.要使用 ctypes ,你首先要确保你要访问的C代码已经被编译到和Python解释器兼容(同样的架构.字大小.编译器等)的某个共享库中了.为了进行本节的演示,假设你有一个共享库名字叫 libsample.so 

Python Cookbook(第3版)中文版:15.12 将函数指针转换为可调用对象

15.12 将函数指针转换为可调用对象? 问题? 你已经获得了一个被编译函数的内存地址,想将它转换成一个Python可调用对象,这样的话你就可以将它作为一个扩展函数使用了. 解决方案? ctypes 模块可被用来创建包装任意内存地址的Python可调用对象.下面的例子演示了怎样获取C函数的原始.底层地址,以及如何将其转换为一个可调用对象: >>> import ctypes >>> lib = ctypes.cdll.LoadLibrary(None) >>

Python Cookbook(第3版)中文版:14.12 调试基本的程序崩溃错误

14.12 调试基本的程序崩溃错误? 问题? 你的程序崩溃后该怎样去调试它? 解决方案? 如果你的程序因为某个异常而崩溃,运行 python3 -i someprogram.py 可执行简单的调试.-i 选项可让程序结束后打开一个交互式shell.然后你就能查看环境,例如,假设你有下面的代码: # sample.py def func(n): return n + 10 func('Hello') 运行 python3 -i sample.py 会有类似如下的输出: bash % python3

python书籍推荐:Python Cookbook第三版中文

所属网站分类: 资源下载 > python电子书 作者:熊猫烧香 链接:http://www.pythonheidong.com/blog/article/44/ 来源:python黑洞网 内容简介 <Python Cookbook(第3版)中文版>介绍了Python应用在各个领域中的一些使用技巧和方法,其主题涵盖了数据结构和算法,字符串和文本,数字.日期和时间,迭代器和生成器,文件和I/O,数据编码与处理,函数,类与对象,元编程,模块和包,网络和Web编程,并发,实用脚本和系统管理,测

python cookbook第三版学习笔记七:python解析csv,json,xml文件

CSV文件读取: Csv文件格式如下:分别有2行三列. 访问代码如下: f=open(r'E:\py_prj\test.csv','rb') f_csv=csv.reader(f) for f in f_csv:     print f 在这里f是一个元组,为了访问某个字段,需要用索引来访问对应的值,如f[0]访问的是first,f[1]访问的是second,f[2]访问的是third. 用列索引的方式很难记住.一不留神就会搞错.可以考虑用对元组命名的方式 这里介绍namedtuple的方法.

python cookbook第三版学习笔记五:datetime

Python中表示时间的模块是datetime,引入下面的模块 from datetime import datetime,timedelta print datetime.today()  #打印出当前的时间 E:\python2.7.11\python.exe E:/py_prj/python_cookbook.py 2017-04-26 21:58:05.663000 我们还可以对时间进行加减操作.这里要用到timedelta模块 这个模块有5个重要参数分别是days,minutes,se