1. 使用.pyx改写python程序
在纯净模式中,我的代码实现手段很有限:
- 只能直接使用python模块和对象
- 无法使用c++的容器和算法
- 无法使用cimport
%load_ext Cython
%%cython # distutils: language=c++ from libcpp.vector cimport vector from libcpp.string cimport string #from libc.stdio cimport printf cpdef print_vect(string content): cdef vector[int] vect cdef int i for i in range(10): vect.push_back(i) for i in vect: #printf("%d",vect[i]) print(vect[i]) for i in content: #printf("%d",vect[i]) print(i)
print_vect("黑龙江".encode())
0 1 2 3 4 5 6 7 8 9 -23 -69 -111 -23 -66 -103 -26 -79 -97
2. Cython语法
Cython是python的超集,所以python解释器无法解释cython代码,必须编译才可以.
2.1. 静态化参数
Cython是一个Python编译器。这意味着它可以编译普通的Python代码,而不会有任何改变(除了一些尚未支持的语言功能之外,还有一些明显的例外).然而,对于性能关键代码,添加静态类型声明通常是有帮助的,因为它们将允许Cython退出Python代码的动态特性,并生成更简单更快的C代码.但是,必须注意的是,类型声明会使源代码更加冗长,因而可读性更低.因此,不要在没有正当理由的情况下使用它们,例如基准测试证明他们在性能关键部分真正使代码更快速地使用它们是不鼓励的.通常在正确的地方有几种类型可以走很长的路.所有C类型都可用于类型声明:整数和浮点类型,复数,结构体,联合和指针类型. Cython可以在分配类型之间自动正确地进行转换.这也包括Python的任意大小的整数类型,其中转换为C类型的值溢出会在运行时引发Python OverflowError
.(但是,在进行算术时,不会检查是否溢出).在这种情况下,生成的C代码将正确安全地处理C类型的依赖于平台的大小.
2.1.1. 在python函数中使用c语言的类型指明翻译为C语言后的参数类型
由于这些参数被传递到Python声明的函数中,它们会转换为指定的C类型值.但这只适用于数字和字符串类型
%%cython def f(double x): return x**2-x def integrate_f(double a, double b, int N): cdef int i cdef double s, dx s = 0 dx = (b-a)/N for i in range(N): s += f(a+i*dx) return s * dx
f(1.2)
0.24
2.2. C级别申明
作为一种动态语言,Python鼓励了一种根据方法和属性考虑类和对象的编程风格,而不仅仅局限于类层次结构中.这可以使Python成为一种非常轻松和舒适的语言,用于快速开发,但有一个代价 -- 管理数据类型的"繁文缛节"被转储到翻译器上.在运行时,解释器在搜索命名空间,获取属性和解析参数和关键字元组方面做了大量工作。与"早期绑定"语言(如C++)相比,这种运行时"后期绑定"是Python相对较慢的主要原因.然而使用Cython可以通过使用"早期绑定"编程技术获得显着的加速.
2.2.1. cdef语句用于创建C级声明
- 申明变量
cdef int i, j, k cdef float f, g[42], *h
- 申明结构体
cdef struct Grail: int age float volume
- 申明联合体
cdef union Food: char *spam float *eggs
- 申明枚举
%%cython # distutils: language=c++ from libcpp.vector cimport vector from libcpp.string cimport string #from libc.stdio cimport printf cpdef print_vect(string content): cdef vector[int] vect cdef int i for i in range(10): vect.push_back(i) for i in vect: #printf("%d",vect[i]) print(vect[i]) for i in content: #printf("%d",vect[i]) print(i)
0
- 申明函数
%%cython # distutils: language=c++ from libcpp.vector cimport vector from libcpp.string cimport string #from libc.stdio cimport printf cpdef print_vect(string content): cdef vector[int] vect cdef int i for i in range(10): vect.push_back(i) for i in vect: #printf("%d",vect[i]) print(vect[i]) for i in content: #printf("%d",vect[i]) print(i)
1
cdef
关键字指定早期绑定,默认是私有的,只有在申明时指定public才会暴露
2.3. 类型转换
在cython中使用<xxx>yyy
操作符来进行类型转换,其使用方式与C中类似.
%%cython # distutils: language=c++ from libcpp.vector cimport vector from libcpp.string cimport string #from libc.stdio cimport printf cpdef print_vect(string content): cdef vector[int] vect cdef int i for i in range(10): vect.push_back(i) for i in vect: #printf("%d",vect[i]) print(vect[i]) for i in content: #printf("%d",vect[i]) print(i)
2
值得注意的是cython中python的bool
类型会转化为bint
,而python中的自定义类的实例则对应的object
2.3.1. 类型检测
和C中类似,类型转换时使用<xxx?>yyy
会先进行检测
2.4. 字符串
C级别无论是字符数组还是c++的string,都只接收python的bytes作为转换来源.返回的也只会转换为bytes.因此需要注意.
2.5. 函数,方法申明
Cython中有三种类型的函数声明.
2.5.1. Python的可调用对象(def)
这种类型的函数特点:
- 使用
def
申明 - 可以被Python解释器调用
- 可以被Python对象调用
- 返回Python对象
- 参数可以静态化
2.5.2. C的可调用对象 (cdef)
这种类型的函数特点:
- 用
cdef
语句声明 - 无法在python解释器中访问
- 可以被Python对象或C值调用
- 内部变量必须申明
- 可以返回Python对象或C值
2.5.3. Python和C的可调用(cpdef)
这种类型的函数特点:
- 用cpdef语句声明.
- 可以从任何地方调用
- 当从其他Cython代码调用时,使用更快的C调用约定
2.5.4. cython的内置函数
Cython将对大多数内置函数的调用编译为对相应的Python / C API例程的直接调用,使它们特别快。 只有使用这些名称的直接函数调用已优化.如果你使用这些名称中的一个假设它是一个Python对象,例如将它分配给一个Python变量,然后调用它,那么该调用将作为一个Python函数调用.
内置函数 | 返回类型 | 相当于Python/C API中的类型 |
---|---|---|
abs(obj) | object, double, ... | PyNumber_Absolute, fabs, fabsf, ... |
callable(obj) | bint | PyObject_Callable |
delattr(obj, name) | None | PyObject_DelAttr |
exec(code, [glob, [loc]]) | object | |
dir(obj) | list | PyObject_Dir |
divmod(a, b) | tuple | PyNumber_Divmod |
getattr(obj, name, [default]) | object | PyObject_GetAttr |
hasattr(obj, name) | bint | PyObject_HasAttr |
hash(obj) | int / long | PyObject_Hash |
intern(obj) | object | Py*_InternFromString |
isinstance(obj, type) | bint | PyObject_IsInstance |
issubclass(obj, type) | bint | PyObject_IsSubclass |
iter(obj, [sentinel]) | object | PyObject_GetIter |
len(obj) | Py_ssize_t | PyObject_Length |
pow(x, y, [z]) | object | PyNumber_Power |
reload(obj) | object | PyImport_ReloadModule |
repr(obj) | object | PyObject_Repr |
setattr(obj, name) | void | PyObject_SetAttr |
2.6. 类申明(扩展类型)
cython扩展类型可以使用cdef class
来定义.
2.6.1. 属性
其中的元素可以使用cdef
来定义,默认是私有的,但可以使用public
或者readonly
关键字指定其封装形式.
%%cython # distutils: language=c++ from libcpp.vector cimport vector from libcpp.string cimport string #from libc.stdio cimport printf cpdef print_vect(string content): cdef vector[int] vect cdef int i for i in range(10): vect.push_back(i) for i in vect: #printf("%d",vect[i]) print(vect[i]) for i in content: #printf("%d",vect[i]) print(i)
3
%%cython # distutils: language=c++ from libcpp.vector cimport vector from libcpp.string cimport string #from libc.stdio cimport printf cpdef print_vect(string content): cdef vector[int] vect cdef int i for i in range(10): vect.push_back(i) for i in vect: #printf("%d",vect[i]) print(vect[i]) for i in content: #printf("%d",vect[i]) print(i)
4
%%cython # distutils: language=c++ from libcpp.vector cimport vector from libcpp.string cimport string #from libc.stdio cimport printf cpdef print_vect(string content): cdef vector[int] vect cdef int i for i in range(10): vect.push_back(i) for i in vect: #printf("%d",vect[i]) print(vect[i]) for i in content: #printf("%d",vect[i]) print(i)
5
%%cython # distutils: language=c++ from libcpp.vector cimport vector from libcpp.string cimport string #from libc.stdio cimport printf cpdef print_vect(string content): cdef vector[int] vect cdef int i for i in range(10): vect.push_back(i) for i in vect: #printf("%d",vect[i]) print(vect[i]) for i in content: #printf("%d",vect[i]) print(i)
6
%%cython # distutils: language=c++ from libcpp.vector cimport vector from libcpp.string cimport string #from libc.stdio cimport printf cpdef print_vect(string content): cdef vector[int] vect cdef int i for i in range(10): vect.push_back(i) for i in vect: #printf("%d",vect[i]) print(vect[i]) for i in content: #printf("%d",vect[i]) print(i)
7
%%cython # distutils: language=c++ from libcpp.vector cimport vector from libcpp.string cimport string #from libc.stdio cimport printf cpdef print_vect(string content): cdef vector[int] vect cdef int i for i in range(10): vect.push_back(i) for i in vect: #printf("%d",vect[i]) print(vect[i]) for i in content: #printf("%d",vect[i]) print(i)
8
%%cython # distutils: language=c++ from libcpp.vector cimport vector from libcpp.string cimport string #from libc.stdio cimport printf cpdef print_vect(string content): cdef vector[int] vect cdef int i for i in range(10): vect.push_back(i) for i in vect: #printf("%d",vect[i]) print(vect[i]) for i in content: #printf("%d",vect[i]) print(i)
9
print_vect("黑龙江".encode())
0
print_vect("黑龙江".encode())
1
%%cython # distutils: language=c++ from libcpp.vector cimport vector from libcpp.string cimport string #from libc.stdio cimport printf cpdef print_vect(string content): cdef vector[int] vect cdef int i for i in range(10): vect.push_back(i) for i in vect: #printf("%d",vect[i]) print(vect[i]) for i in content: #printf("%d",vect[i]) print(i)
8
print_vect("黑龙江".encode())
3
print_vect("黑龙江".encode())
4
print_vect("黑龙江".encode())
5
%%cython # distutils: language=c++ from libcpp.vector cimport vector from libcpp.string cimport string #from libc.stdio cimport printf cpdef print_vect(string content): cdef vector[int] vect cdef int i for i in range(10): vect.push_back(i) for i in vect: #printf("%d",vect[i]) print(vect[i]) for i in content: #printf("%d",vect[i]) print(i)
6
print_vect("黑龙江".encode())
7
print_vect("黑龙江".encode())
8
2.6.2. 特性
cython特性除了一般python语句一样的装饰器语法外,还可以使用如下特殊定义方式,两者效果一致.
print_vect("黑龙江".encode())
9
__get__()
,__set__()
和__del__()
方法都是可选的.如果省略,属性访问会引发异常.
不推荐这种写法,因为看起来和python差别太大
0 1 2 3 4 5 6 7 8 9 -23 -69 -111 -23 -66 -103 -26 -79 -97
0
0 1 2 3 4 5 6 7 8 9 -23 -69 -111 -23 -66 -103 -26 -79 -97
1
0 1 2 3 4 5 6 7 8 9 -23 -69 -111 -23 -66 -103 -26 -79 -97
2
2.6.3. 方法
Rectangle
中_area
是C级别的函数,不可被访问,所以需要使用area
方法来封装.不过通常是使用cpdef
直接实现的
0 1 2 3 4 5 6 7 8 9 -23 -69 -111 -23 -66 -103 -26 -79 -97
3
%%cython # distutils: language=c++ from libcpp.vector cimport vector from libcpp.string cimport string #from libc.stdio cimport printf cpdef print_vect(string content): cdef vector[int] vect cdef int i for i in range(10): vect.push_back(i) for i in vect: #printf("%d",vect[i]) print(vect[i]) for i in content: #printf("%d",vect[i]) print(i)
4
2.6.4. 方法重载
在扩展类型中同一申明方式的可以相互重载,而不同申明方式的则有一套优先级:
cpdef
可以重载cdef
,而反过来就不行def
可以重载cpdef
,而反过来就不行
0 1 2 3 4 5 6 7 8 9 -23 -69 -111 -23 -66 -103 -26 -79 -97
5
0 1 2 3 4 5 6 7 8 9 -23 -69 -111 -23 -66 -103 -26 -79 -97
6
0 1 2 3 4 5 6 7 8 9 -23 -69 -111 -23 -66 -103 -26 -79 -97
7
0 1 2 3 4 5 6 7 8 9 -23 -69 -111 -23 -66 -103 -26 -79 -97
8
0 1 2 3 4 5 6 7 8 9 -23 -69 -111 -23 -66 -103 -26 -79 -97
9
%%cython def f(double x): return x**2-x def integrate_f(double a, double b, int N): cdef int i cdef double s, dx s = 0 dx = (b-a)/N for i in range(N): s += f(a+i*dx) return s * dx
0
%%cython def f(double x): return x**2-x def integrate_f(double a, double b, int N): cdef int i cdef double s, dx s = 0 dx = (b-a)/N for i in range(N): s += f(a+i*dx) return s * dx
1
%%cython def f(double x): return x**2-x def integrate_f(double a, double b, int N): cdef int i cdef double s, dx s = 0 dx = (b-a)/N for i in range(N): s += f(a+i*dx) return s * dx
2
2.7. 继承
基类的完整定义必须可用于Cython,因此如果基类是内置类型,它必须先前已声明为extern扩展类型.如果基类型在另一个Cython模块中定义,则必须声明为extern扩展类型或使用cimport语句导入.
一个扩展类型只能有一个基类(cython的扩展类不支持多重继承).但可以被python类继承,这种继承支持多继承.
有一种方法可以防止扩展类型在Python中被子类化.这是通过final
指令完成的,通常使用装饰器在扩展类型上设置
%%cython def f(double x): return x**2-x def integrate_f(double a, double b, int N): cdef int i cdef double s, dx s = 0 dx = (b-a)/N for i in range(N): s += f(a+i*dx) return s * dx
3
2.8. 扩展类型快速实例化
Cython提供了两种方法来加速扩展类型的实例化.
- 第一个是直接调用
__new__()
特殊静态方法,如从Python中所知。对于例子扩展类型Penguin,可以使用以下代码:
%%cython def f(double x): return x**2-x def integrate_f(double a, double b, int N): cdef int i cdef double s, dx s = 0 dx = (b-a)/N for i in range(N): s += f(a+i*dx) return s * dx
4
%%cython def f(double x): return x**2-x def integrate_f(double a, double b, int N): cdef int i cdef double s, dx s = 0 dx = (b-a)/N for i in range(N): s += f(a+i*dx) return s * dx
5
%%cython def f(double x): return x**2-x def integrate_f(double a, double b, int N): cdef int i cdef double s, dx s = 0 dx = (b-a)/N for i in range(N): s += f(a+i*dx) return s * dx
6
%%cython def f(double x): return x**2-x def integrate_f(double a, double b, int N): cdef int i cdef double s, dx s = 0 dx = (b-a)/N for i in range(N): s += f(a+i*dx) return s * dx
7
%%cython def f(double x): return x**2-x def integrate_f(double a, double b, int N): cdef int i cdef double s, dx s = 0 dx = (b-a)/N for i in range(N): s += f(a+i*dx) return s * dx
8
%%cython def f(double x): return x**2-x def integrate_f(double a, double b, int N): cdef int i cdef double s, dx s = 0 dx = (b-a)/N for i in range(N): s += f(a+i*dx) return s * dx
9
f(1.2)
0
f(1.2)
1
__new__()
实例化的对象会不运行__init__()
只会运行__cinit__()
第二个性能改进适用于经常在一行中创建和删除的类型,以便他们可以从freelist中受益.
Cython为此提供了装饰器
@cython.freelist(N)
,它为给定类型创建了一个静态大小的N个实例的freelist
.例:
f(1.2)
2
f(1.2)
3
f(1.2)
4
2.9. 特殊方法
cython也支持特殊方法,它支持的特殊方法可在这里看到
扩展类型的特殊方法必须用def
,而不是cdef
声明,这不会影响他们的性能 -- Python使用不同的调用约定来调用这些特殊方法.
这边列举几个比较中要的:
2.9.1. 初始化方法:__cinit__() 和__init__()
有两种方法与初始化对象有关.
__cinit__()
方法是应该执行对象的基本C级初始化,包括对象将拥有的任何C数据结构的分配.你需要小心你在
__cinit__()
方法中做什么,因为对象可能还不是完全有效的Python对象.因此,你应该小心调用任何Python可能触摸对象的操作,特别是其方法.在调用
__cinit__()
方法时,已经为对象分配了内存,并且任何C属性已初始化为0或null。(任何Python属性也被初始化为None,但是你可能不应该依赖它.)你的__cinit__()
方法一定只会被调用一次.如果扩展类型有基类,那么在调用
__cinit __()
方法之前,将自动调用基类的__cinit__()
方法,你不能显式调用基类的__cinit__()
方法。如果需要将修改的参数列表传递给基类,则必须在__init__()
方法中执行初始化的相关部分(其中调用继承方法的正常规则适用).__init__()
在
__cinit__()
方法中不能安全完成的任何初始化都应该在__init__()
方法中完成.当调用__init __()
时,对象是一个完全有效的Python对象,所有操作都是安全的.在某些情况下,__init__()
可能被多次调用或根本不被调用.
传递给构造函数的任何参数都将传递给__cinit __()
方法和__init__()
方法。如果你预计在Python中继承扩展类型,可能会将参数以*和**参数
的形式传给__cinit__()
方法,以便它可以接受和忽略额外的参数.否则,任何具有带有不同签名的__init __()
的Python子类都必须覆盖__new __()
以及__init __()
,明显这样很不友好.或者,为了方便起见,如果你声明你的__cinit__()
方法没有参数(除了self
),它将简单地忽略传递给构造函数的任何额外的参数.这种方式可能更加保险.
2.10. 析构方法:__dealloc__()
与__cinit__()
方法的逆向方法是__dealloc__()
方法。__cinit__()
方法中显式分配内存的任何C数据(例如通过malloc分配的空间)应在__dealloc__()
方法中释放.你需要小心你在__dealloc __()
方法中的操作.在调用__dealloc__()
方法时,对象可能已经被部分销毁,并且对于Python而言可能不处于有效状态,如果你坚持只是释放C数据是最好的选择.
你不需要担心释放对象的Python属性,因为在__dealloc __()
方法返回后,它将由Cython完成. 当子类化扩展类型时,请注意,超类的__dealloc__()
方法将始终被调用,即使它被覆盖.这与典型的Python行为不同.
注意:
扩展类型没有__del__()
方法。
2.11. 逻辑运算方法
算术运算符方法(如__add__()
)的行为与Python对应方法不同。这些方法没有单独的“反转”版本(__radd __()
等).相反,如果第一个操作数不能执行操作,则调用第二个操作数的相同方法,操作数的顺序相同.
2.12. 运算比较操作
对于不同的比较操作(__eq__()
,__le__()
等等)Cython没有单独的方法.而是有一个方法__richcmp __()
,它接受一个整数,指示要执行哪种操作,如下所示:
操作 | 指示 |
---|---|
< | 0 |
== | 2 |
> | 4 |
<= | 1 |
!= | 3 |
>= | 5 |
2.13. 错误处理
如果你不做任何特殊的事情,用cdef声明的函数不返回任何Python对象,这样这个cdef函数就没有办法向其调用者报告其Python异常.如果在此类函数中检测到异常,则会打印一条警告消息,并忽略该异常. 如果你想要一个不返回Python对象的C函数能够将异常传播给它的调用者,你需要声明一个异常值。这里是一个例子:
f(1.2)
5
使用此声明,每当spam()
函数内发生异常时,它将立即返回值-1
.此外,每当对spam()
的调用返回-1
时,将假定异常已经发生并将被传播.
当为函数声明异常值时,不应显式或隐式返回该值.特别是,如果异常返回值是一个False
值,那么你应该确保函数永远不会通过隐式或空返回终止.
如果所有可能的返回值都是合法的,并且你不能完全保留一个用于信号错误,则可以使用异常值声明的替代形式:
f(1.2)
6
'?'号表示-1是个异常值,在这种情况下,Cython通过生成一个函数PyErr_Occurred()
进行返回,从而知道该函数发生了异常值.
还有第三种定义方式:
f(1.2)
7
这种形式导致Cython在每次调用spam()后生成对PyErr_Occurred()
的调用,而不管它返回什么值。如果你有一个函数返回void需要传播错误,你将不得不使用这种形式,因为没有任何返回值来测试.否则这种形式的定义应该尽量少用.
需要注意的是:
异常值只能为返回
- 整数,
- 枚举,
- 浮点
- 指针类型的函数声明.
并且该值必须是常量表达式。
void函数只能使用except *
形式.
异常值规范是函数签名的一部分。如果将指针作为参数传递给函数或将其指定给变量,则声明的参数或变量的类型必须具有相同的异常值规范(或缺少).下面是一个具有异常值的指针到函数声明的示例:
f(1.2)
8
2.14. 模块导入
cython中的导入方式有3种:
python的
import
用于导入python模块,一般只在实现文件中导入
cython的
cimport
用于导入cpython中
.pxd
文件中申明的内容和一些cpython封装好的标准模块,可以在申明文件或者实现文件中导入cython的
include
include
语句用于导入.pxi
文件.这种语法类似C/C++
中的#include
,是直接将目标文件内容复制到当前位置
2.14.1. 使用C++的stl和C标准库
大多数C++标准库的容器已在位于/Cython /Includes/libcpp
的pxd文件中声明.这些容器是:
- deque 双向队列
- list 列表
- map 映射
- pair 对
- queue 队列
- set集合
- stack栈
- vector 向量
这些容器的使用方法可以看C++的stl部分.
因此现在想用这些容器只需简单的cimport进来即可
f(1.2)
9
0.24
0
现在在Cython中STL容器会从相应的Python内建类型中强制转换。转换通过对类型化变量(包括类型化函数参数)的赋值或显式转换触发,例如:
0.24
1
0.24
2
以下强制可用:
Python type => | C++ type => | Python type |
---|---|---|
bytes | std::string | bytes |
iterable | std::vector | list |
iterable | std::list | list |
iterable | std::set | set |
iterable (len 2) | std::pair | tuple (len 2) |
所有转换都会创建一个新的容器并将数据复制到该容器中.容器中的项目会自动转换为相应的类型,包括递归转换容器内的容器,例如字符串map
转换为vector
. 支持在stl容器(或实际上任何类与begin()
和end()
方法返回支持递增,取消引用和比较的对象)通过for语法支持.包括列表解析.例如如下代码:
0.24
3
0.24
4
0.24
5
0.24
6
2.15. Cython中的array
Python有一个内置1维数组的原始类型的动态数组模块.可以从Cython中访问Python数组的底层C数组.同时,它们是普通的Python对象,可以存储在列表中并在多进程之间进行序列化.
与malloc()
和free()
的手动控制内存方法相比,这提供了对Python的安全和自动内存管理,与Numpy数组相比,无需安装依赖关系,因为数组模块内置于cPython和cython中.
2.15.1. 使用内存视图的安全使用方式
注意:cython中需要同时导入cython级别的数组和常规Python数组对象引入到命名空间中.
当一个Python数组被分配给一个类型为内存视图的变量时,构造内存视图将会有一些微小的开销.但是,从这个角度来看,变量可以传递给其他函数而不需要开销,只要它被输入:
0.24
7
%%cython # distutils: language=c++ from libcpp.vector cimport vector from libcpp.string cimport string #from libc.stdio cimport printf cpdef print_vect(string content): cdef vector[int] vect cdef int i for i in range(10): vect.push_back(i) for i in vect: #printf("%d",vect[i]) print(vect[i]) for i in content: #printf("%d",vect[i]) print(i)
6
0.24
9
cdef int i, j, k cdef float f, g[42], *h
0
2.15.2. 零开销,不安全访问原始C指针
为了避免任何开销,并且能够将C指针传递给其他函数,可以以底层的连续数组作为指针.没有类型或边界检查,所以要小心使用正确的类型和签名.
请注意,数组对象上的任何长度变化操作都可能使指针无效.
cdef int i, j, k cdef float f, g[42], *h
1
%%cython # distutils: language=c++ from libcpp.vector cimport vector from libcpp.string cimport string #from libc.stdio cimport printf cpdef print_vect(string content): cdef vector[int] vect cdef int i for i in range(10): vect.push_back(i) for i in vect: #printf("%d",vect[i]) print(vect[i]) for i in content: #printf("%d",vect[i]) print(i)
6
2.15.3. 克隆,扩展数组
为避免使用Python模块中的数组构造函数,可以创建与模板类型相同的新数组,并预先分配给定数量的元素.数组在请求时初始化为零.
cdef int i, j, k cdef float f, g[42], *h
3
一个数组也可以被扩展和调整大小;这避免了重复的内存重新分配,如果元素被逐个附加或删除,则会发生这种重新分配.
cdef int i, j, k cdef float f, g[42], *h
4
2.15.4. 相关接口
将内容强制转换
- data.as_voidptr
- data.as_chars
- data.as_schars
- data.as_uchars
- data.as_shorts
- data.as_ushorts
- data.as_ints
- data.as_uints
- data.as_longs
- data.as_ulongs
- data.as_longlongs # requires Python >=3
- data.as_ulonglongs # requires Python >=3
- data.as_floats
- data.as_doubles
- data.as_pyunicodes
操作的相关函数
int resize(array self, Py_ssize_t n) except -1
快速调整大小/ realloc.不适合重复,小增量;将底层数组调整到正确的请求量.
int resize_smart(array self, Py_ssize_t n) except -1
小增量更加有效;使用提供摊销线性时间附加的增长模式.
cdef inline array clone(array template, Py_ssize_t length, bint zero)
给定一个模板数组,快速创建一个新数组.类型将与模板相同.如果为零,则将使用零初始化新数组.
cdef inline array copy(array self)
复制一个数组
cdef inline int extend_buffer(array self, char* stuff, Py_ssize_t n) except -1
对相同类型的新数据(例如,相同数组类型),高效附加n个元素数量的长度cdef inline int extend(array self, array other) except -1
使用另一个数组的数据扩展数组;类型必须匹配。cdef inline void zero(array self)
将数组内容全部置0
2.16. Memoryviews内存视图
类型化的内存视图可以有效地访问内存缓冲区,例如NumPy数据库中的缓冲区,而不会导致任何Python开销. Memoryview类似于当前的NumPy数组缓冲区支持(np.ndarray [np.float64_t,ndim = 2]),但它们具有更多的功能和更清晰的语法.内存访问比旧的NumPy数组缓冲区支持更通用,因为它们可以处理更多种类的数组数据源.例如,他们可以处理C数组和Cython数组类型(Cython数组).可以在任何上下文(函数参数,模块级,cdef类属性等)中使用内存视图,并且可以从几乎任何通过PEP 3118
缓冲区接口暴露可写缓冲区的对象获得.
如果习惯使用NumPy,以下示例应该让您开始使用Cython内存视图.
cdef int i, j, k cdef float f, g[42], *h
5
cdef int i, j, k cdef float f, g[42], *h
6
2.16.1. 基本语法
内存视图使用Python切片语法,与NumPy类似。要在一维int缓冲区上创建一个完整的视图:
- 一维视图:
cdef int i, j, k cdef float f, g[42], *h
7
- 三维视图:
cdef int i, j, k cdef float f, g[42], *h
8
- 2D视图将缓冲区的第一维度限制为从第二个(索引1)开始的100行,然后每秒(奇数)行跳过:
cdef int i, j, k cdef float f, g[42], *h
9
- 这也可以方便地作为函数参数
cdef struct Grail: int age float volume
0
该参数的not None
声明自动拒绝无值作为输入,否则将允许.默认情况下允许None的原因是它方便地用于返回参数:
cdef struct Grail: int age float volume
1
Cython将自动拒绝不兼容的缓冲区,例如将三维缓冲区传递到需要二维缓冲区的函数将引发ValueError
。
2.16.2. 索引
在Cython中,内存视图上的索引访问将自动转换为内存地址。以下代码向其中请求一个二维内存视图的C类型的项目和索引:
cdef struct Grail: int age float volume
2
负指数也从各自的维度结束起计算:
cdef struct Grail: int age float volume
3
以下函数循环遍历2D数组的每个维度,并为每个项添加1:
cdef struct Grail: int age float volume
4
可以使用或不使用GIL进行索引和切片。它基本上像NumPy一样工作.如果为每个维指定了索引,您将获得基本类型的元素(例如int).否则将获得一个新的视图.省略号意味着可以为每个未指定维度获得连续的切片:
cdef struct Grail: int age float volume
5
2.16.3. 复制
内存视图可以复制
cdef struct Grail: int age float volume
6
它们也可以用copy()和copy_fortran()方法复制
2.16.4. 转置
在大多数情况下内存视图可以以与NumPy切片相同的方式进行转置:
cdef struct Grail: int age float volume
7
这一操作会给出一个新的,转置过的数据视图。调换要求存储器视图的所有维度都具有直接访问存储器布局(即,通过指针不存在任何指令).
2.17. 内存视图与数组
这些类型对象的内存视图可以转换为Python memoryview对象(cython.view.memoryview
).这些Python对象是可索引的,可切片的并且以与原始内存访问相同的方式进行转座。它们也可以随时转换回Cython-space memoryviews
.
它们具有以下属性:
- shape: size in each dimension, as a tuple.
- strides: stride along each dimension, in bytes.
- suboffsets
- ndim: number of dimensions.
- size: total number of items in the view (product of the shape).
- itemsize: size, in bytes, of the items in the view.
- nbytes: equal to size times itemsize.
- base
- T
当然还有上述的T属性(Transpose)。这些属性具有与NumPy相同的语义.例如,要检索原始对象:
cdef struct Grail: int age float volume
8
cdef struct Grail: int age float volume
9
请注意,此示例返回从中获取视图的原始对象,同时视图已被重新生成.
每当复制Cython内存视图(使用任何copy或copy_fortran方法)时,都会获得新创建的cython.view.array对象的新内存视图.这个数组也可以手动使用,并会自动分配一个数据块.它可以随后被分配给C或Fortran连续片(或跨片).它可以像下面:
cdef union Food: char *spam float *eggs
0
它还需要一个可选的参数模式('c'或'fortran')和一个布尔的allocate_buffer
,它指示当超出范围时是否应该分配和释放缓冲区:
cdef union Food: char *spam float *eggs
1
还可以将数组的指针或C数组转换为数组:
cdef union Food: char *spam float *eggs
2
当然,也可以立即将cython.view.array
指定给类型化的内存视图切片.可以将C数组直接分配给内存视图切片:
cdef union Food: char *spam float *eggs
3
数组可以像Python空间一样可索引和可切换,就像memoryview
对象一样,并且具有与memoryview
对象相同的属性.
cython.view.array
的替代方法是Python标准库中的数组模块。在Python 3中,array.array类型本身支持缓冲区接口,所以在没有额外的设置的情况下,memoryviews
就可以工作.
cdef union Food: char *spam float *eggs
4
请注意,cimport还为阵列类型启用旧的缓冲区语法.因此,以下内容也起作用:
cdef union Food: char *spam float *eggs
5
2.18. 内存控制
动态内存分配大多时候在Python中不是一个问题.一切都是一个对象,引用计数系统和垃圾收集器会在不再使用系统时自动返回内存.当涉及到更低级别的数据缓冲区时,Cython通过NumPy,内存视图或Python的stdlib数组类型,为简单类型的(多维)数组提供了特殊的支持.它们是全功能,带垃圾收集,比C中的裸指针更容易工作;同时仍然保持速度和静态类型的好处.然而,在某些情况下,这些对象仍然会产生不可接受的开销,从而可以在C中进行手动内存管理.
简单的C语言值和结构(例如局部变量cdef double x)通常分配在堆栈上并通过值传递.但是对于较大和更复杂的对象(例如动态大小的双精度列表),必须手动请求内存并发布. C为此提供了malloc()
,realloc()
和free()
的功能,可以从clibc.stdlib
导入cython
.他们的签名是:
cdef union Food: char *spam float *eggs
6
下面是一个简单的例子:
cdef union Food: char *spam float *eggs
7
cdef union Food: char *spam float *eggs
8
cdef union Food: char *spam float *eggs
9
请注意,在Python堆上分配内存的C-API函数通常比上面的低级C函数更为优先,因为它们提供的内存实际上是在Python的内部存储器管理系统中解决的.它们还对较小的内存块进行了特殊优化,从而通过避免昂贵的操作系统调用来加快其分配.
C-API函数可以在cpython.mem
标准声明文件中找到:
%%cython # distutils: language=c++ from libcpp.vector cimport vector from libcpp.string cimport string #from libc.stdio cimport printf cpdef print_vect(string content): cdef vector[int] vect cdef int i for i in range(10): vect.push_back(i) for i in vect: #printf("%d",vect[i]) print(vect[i]) for i in content: #printf("%d",vect[i]) print(i)
00
它们的接口和用法与相应的低级C函数的接口和用法相同.
需要记住的一个重要的事情是,使用malloc()
或PyMem_Malloc()
获取的内存块必须在不再使用时对其调用free()
或PyMem_Free()
进行手动释放(并且必须始终使用匹配类型的自由功能).否则,直到python进程退出才会被回收.这被称为内存泄漏.
如果一块内存需要比可以由try...finally
块管理的更长的生命周期,另一个有用的习惯是将其生命周期与Python对象相结合,以利用Python运行时的内存管理,例如:
%%cython # distutils: language=c++ from libcpp.vector cimport vector from libcpp.string cimport string #from libc.stdio cimport printf cpdef print_vect(string content): cdef vector[int] vect cdef int i for i in range(10): vect.push_back(i) for i in vect: #printf("%d",vect[i]) print(vect[i]) for i in content: #printf("%d",vect[i]) print(i)
01
2.19. 去除gil限制
Cython提供了解除和使用全局锁(GIL)的设施。当从多线程代码调用(外部C)代码可能会阻止或希望从(本地)C线程回调使用Python时,这可能很有用.显然,释放GIL应该只对线程安全的代码或使用其他防止种族条件和并发问题的保护措施的代码进行.注意,获取GIL是一个阻塞线程同步操作,因此是潜在的昂贵开销。可能不值得发布GIL进行微小的计算。通常,并行代码中的I / O操作和实质性计算将从中受益.
2.19.1. 释放GIL
可以使用nogil语句
释放GIL周围的一段代码:
%%cython # distutils: language=c++ from libcpp.vector cimport vector from libcpp.string cimport string #from libc.stdio cimport printf cpdef print_vect(string content): cdef vector[int] vect cdef int i for i in range(10): vect.push_back(i) for i in vect: #printf("%d",vect[i]) print(vect[i]) for i in content: #printf("%d",vect[i]) print(i)
02
在语句正文中的代码不得以任何方式引发异常或操纵Python对象,并且不得先调用任何操作Python对象的操作,从而无需先重新获取GIL.例如,Cython在编译时验证这些操作,但不能查看外部C函数.它们必须被正确声明为要求或不要求GIL(见下文),以使Cython的检查生效.
2.19.2. 获得GIL
要用作没有GIL执行的C代码的回调的C函数需要在操作Python对象之前获取GIL。这可以通过在函数头中指定gil来完成:
%%cython # distutils: language=c++ from libcpp.vector cimport vector from libcpp.string cimport string #from libc.stdio cimport printf cpdef print_vect(string content): cdef vector[int] vect cdef int i for i in range(10): vect.push_back(i) for i in vect: #printf("%d",vect[i]) print(vect[i]) for i in content: #printf("%d",vect[i]) print(i)
03
如果可以从另一个非Python线程调用回调函数,则必须首先通过调用PyEval_InitThreads()
来初始化GIL。如果你已经在你的模块中使用cython.parallel
,这个已经被照顾了.GIL也可以通过gil语句获得:
%%cython # distutils: language=c++ from libcpp.vector cimport vector from libcpp.string cimport string #from libc.stdio cimport printf cpdef print_vect(string content): cdef vector[int] vect cdef int i for i in range(10): vect.push_back(i) for i in vect: #printf("%d",vect[i]) print(vect[i]) for i in content: #printf("%d",vect[i]) print(i)
04
2.19.3. 声明一个可调用的不受gil限制的的函数
您可以在C函数头或函数类型中指定nogil,以声明在没有GIL的情况下可以安全地调用:
%%cython # distutils: language=c++ from libcpp.vector cimport vector from libcpp.string cimport string #from libc.stdio cimport printf cpdef print_vect(string content): cdef vector[int] vect cdef int i for i in range(10): vect.push_back(i) for i in vect: #printf("%d",vect[i]) print(vect[i]) for i in content: #printf("%d",vect[i]) print(i)
05
当您在Cython中实现这样的函数时,它不能有任何Python参数或Python对象返回类型.此外,涉及Python对象(包括调用Python函数)的任何操作必须首先明确获取GIL,例如:通过使用gil块或调用已经用gil定义的函数.这些限制由Cython检查,如果在nogil代码部分找到任何Python交互,您将收到编译错误.
注意nogil函数注释声明在没有GIL的情况下调用该函数是安全的.完全可以在持有GIL的同时执行它.如果调用者持有该功能,本身并不释放GIL.
用gil来声明一个函数(即获取输入的GIL)也隐含地使它的签名nogil.
2.20. 并行编程(使用openmp)
Cython通过cython.parallel
模块支持本机并行.要使用这种并行性,必须释放GIL(请参阅释放GIL).它目前支持OpenMP,但后来可能会支持更多后端.
cython.parallel.prange([start,] stop[, step][, nogil=False][, schedule=None[, chunksize=None]][, num_threads=None])
此功能可用于并行循环.OpenMP自动启动一个线程池,并根据所使用的时间表分配工作.步骤不能为0.此功能只能与GIL一起使用.如果nogil为真,则循环将包裹在nogil部分。针对变量自动推断线程位置和裁减。如果您分配给一个prange块中的变量,它将变为lastprivate,这意味着该变量将包含上一次迭代中的值。如果在一个变量上使用一个inplace操作符,那么它会减少,这意味着该变量的线程本地副本的值将随着操作符而减少,并在循环后分配给原始变量。索引变量始终为lastprivate.与块并行分配的变量将在块之后是私有的和不可用的,因为没有连续的最后一个值的概念.
schedule参数会传递给OpenMP,可以是以下之一:
- static静态的:如果提供了一个
chunksize
,迭代将在给定的chunksize
块中提前分发给所有线程。如果没有给出chunksize
,则迭代空间被分成大小相等的块,并且至多一个块预先分配给每个线程。当调度开销重要时,这是最合适的,并且可以将问题减少到已知具有大致相同运行时的大小相同的块. - dynamic动态的:迭代被分发给线程,因为它们请求它们,默认块大小为1.当每个块的运行时间不同而不是预先知道时,这是适用的,因此使用较大数量的较小块来保持所有线程忙.
- guided有指导的:与动态调度一样,迭代被分配给线程,因为它们请求它们,但是随着块大小的减小.每个块的大小与未分配迭代次数除以参与线程数减少到1(或者提供的
chunksize
)成比例.这已超过纯动态调度的优势,事实证明,当最后一个块需要比预期或以其他方式被严重计划更多的时间,使大部分线程开始运行闲置而最后块正在只有线程数量较少的制作. - runtime运行时的:调度和块大小取自运行时调度变量,可以通过
openmp.omp_set_schedule()
函数调用或OMP_SCHEDULE
环境变量进行设置.请注意,这基本上禁用了调度代码本身的任何静态编译时间优化,因此可能会显示比在编译时静态配置相同调度策略时更差的性能.
- static静态的:如果提供了一个
cython.parallel.threadid()
返回线程的ID。对于n个线程,ids的范围为0到n-1
cython.parallel.parallel(num_threads=None)
该指令可以作为with语句的一部分,并行执行代码序列.这对于设置由prange使用的线程本地缓冲区目前是有用的.一个包含的prange将是一个并行的工作共享循环,因此在并行部分中分配的任何变量对于prange也是私有的.并行块中私有的变量在并行块之后不可用.
%%cython # distutils: language=c++ from libcpp.vector cimport vector from libcpp.string cimport string #from libc.stdio cimport printf cpdef print_vect(string content): cdef vector[int] vect cdef int i for i in range(10): vect.push_back(i) for i in vect: #printf("%d",vect[i]) print(vect[i]) for i in content: #printf("%d",vect[i]) print(i)
06
%%cython # distutils: language=c++ from libcpp.vector cimport vector from libcpp.string cimport string #from libc.stdio cimport printf cpdef print_vect(string content): cdef vector[int] vect cdef int i for i in range(10): vect.push_back(i) for i in vect: #printf("%d",vect[i]) print(vect[i]) for i in content: #printf("%d",vect[i]) print(i)
07
%%cython # distutils: language=c++ from libcpp.vector cimport vector from libcpp.string cimport string #from libc.stdio cimport printf cpdef print_vect(string content): cdef vector[int] vect cdef int i for i in range(10): vect.push_back(i) for i in vect: #printf("%d",vect[i]) print(vect[i]) for i in content: #printf("%d",vect[i]) print(i)
08
%%cython # distutils: language=c++ from libcpp.vector cimport vector from libcpp.string cimport string #from libc.stdio cimport printf cpdef print_vect(string content): cdef vector[int] vect cdef int i for i in range(10): vect.push_back(i) for i in vect: #printf("%d",vect[i]) print(vect[i]) for i in content: #printf("%d",vect[i]) print(i)
09
%%cython # distutils: language=c++ from libcpp.vector cimport vector from libcpp.string cimport string #from libc.stdio cimport printf cpdef print_vect(string content): cdef vector[int] vect cdef int i for i in range(10): vect.push_back(i) for i in vect: #printf("%d",vect[i]) print(vect[i]) for i in content: #printf("%d",vect[i]) print(i)
10
%%cython # distutils: language=c++ from libcpp.vector cimport vector from libcpp.string cimport string #from libc.stdio cimport printf cpdef print_vect(string content): cdef vector[int] vect cdef int i for i in range(10): vect.push_back(i) for i in vect: #printf("%d",vect[i]) print(vect[i]) for i in content: #printf("%d",vect[i]) print(i)
11
%%cython # distutils: language=c++ from libcpp.vector cimport vector from libcpp.string cimport string #from libc.stdio cimport printf cpdef print_vect(string content): cdef vector[int] vect cdef int i for i in range(10): vect.push_back(i) for i in vect: #printf("%d",vect[i]) print(vect[i]) for i in content: #printf("%d",vect[i]) print(i)
12
%%cython # distutils: language=c++ from libcpp.vector cimport vector from libcpp.string cimport string #from libc.stdio cimport printf cpdef print_vect(string content): cdef vector[int] vect cdef int i for i in range(10): vect.push_back(i) for i in vect: #printf("%d",vect[i]) print(vect[i]) for i in content: #printf("%d",vect[i]) print(i)
13
%%cython # distutils: language=c++ from libcpp.vector cimport vector from libcpp.string cimport string #from libc.stdio cimport printf cpdef print_vect(string content): cdef vector[int] vect cdef int i for i in range(10): vect.push_back(i) for i in vect: #printf("%d",vect[i]) print(vect[i]) for i in content: #printf("%d",vect[i]) print(i)
14
%%cython # distutils: language=c++ from libcpp.vector cimport vector from libcpp.string cimport string #from libc.stdio cimport printf cpdef print_vect(string content): cdef vector[int] vect cdef int i for i in range(10): vect.push_back(i) for i in vect: #printf("%d",vect[i]) print(vect[i]) for i in content: #printf("%d",vect[i]) print(i)
15
%%cython # distutils: language=c++ from libcpp.vector cimport vector from libcpp.string cimport string #from libc.stdio cimport printf cpdef print_vect(string content): cdef vector[int] vect cdef int i for i in range(10): vect.push_back(i) for i in vect: #printf("%d",vect[i]) print(vect[i]) for i in content: #printf("%d",vect[i]) print(i)
16
%%cython # distutils: language=c++ from libcpp.vector cimport vector from libcpp.string cimport string #from libc.stdio cimport printf cpdef print_vect(string content): cdef vector[int] vect cdef int i for i in range(10): vect.push_back(i) for i in vect: #printf("%d",vect[i]) print(vect[i]) for i in content: #printf("%d",vect[i]) print(i)
17
%%cython # distutils: language=c++ from libcpp.vector cimport vector from libcpp.string cimport string #from libc.stdio cimport printf cpdef print_vect(string content): cdef vector[int] vect cdef int i for i in range(10): vect.push_back(i) for i in vect: #printf("%d",vect[i]) print(vect[i]) for i in content: #printf("%d",vect[i]) print(i)
18
还没有评论,来说两句吧...