ead*_*ead 5 python performance cython python-3.x
看起来,对于Cython的cdef类,使用类特殊方法有时比相同的"常规"方法__setitem__更快,例如比setitem以下快3倍:
%%cython
cdef class CyA:
def __setitem__(self, index, val):
pass
def setitem(self, index, val):
pass
Run Code Online (Sandbox Code Playgroud)
现在:
cy_a=CyA()
%timeit cy_a[0]=3 # 32.4 ns ± 0.195 ns per loop (mean ± std. dev. of 7 runs, 10000000 loops each)
%timeit cy_a.setitem(0,3) # 97.5 ns ± 0.389 ns per loop (mean ± std. dev. of 7 runs, 10000000 loops each)
Run Code Online (Sandbox Code Playgroud)
这既不是Python的"正常"行为,特殊功能甚至更慢(并且比Cython等效的速度慢):
class PyA:
def __setitem__(self, index, val):
pass
def setitem(self, index, val):
pass
py_a=PyA()
%timeit py_a[0]=3 # 198 ns ± 2.51 ns per loop (mean ± std. dev. of 7 runs, 1000000 loops each)
%timeit py_a.setitem(0,3) # 123 ns ± 0.619 ns per loop (mean ± std. dev. of 7 runs, 10000000 loops each)
Run Code Online (Sandbox Code Playgroud)
在Cython中,所有特殊功能都不是这样的:
%%cython
cdef class CyA:
...
def __len__(self):
return 1
def len(self):
return 1
Run Code Online (Sandbox Code Playgroud)
这导致:
cy_a=CyA()
%timeit len(cy_a) # 59.6 ns ± 0.233 ns per loop (mean ± std. dev. of 7 runs, 10000000 loops each)
%timeit cy_a.len() # 66.5 ns ± 0.326 ns per loop (mean ± std. dev. of 7 runs, 10000000 loops each)
Run Code Online (Sandbox Code Playgroud)
即几乎相同的运行时间.
为什么__setitem__(...)比setitem(...)cdef级更快,即使两者都是cython化的?
通用 Python 方法调用有相当多的开销 - Python 查找相关属性(字典查找),确保该属性是可调用对象,并且一旦调用它就会处理结果。这种开销也适用于类的泛型def函数cdef(唯一的区别是该方法的实现是在 C 中定义的)。
但是,可以优化 C/Cython 类上的特殊方法,如下所示:
作为一种快捷方式,
PyTypeObjectPython C API 中定义了许多不同的“槽”——特殊方法的直接函数指针。因为__setitem__实际上有两个可用:PyMappingMethods.mp_ass_subscript对应于通用“映射”调用,以及PySequenceMethods.sq_ass_item,它允许您直接使用 int 作为索引器并对应于 C API 函数PySequence_SetItem。
对于cdef class,Cython 似乎只生成第一个(通用),因此加速并不是int直接传递 C 。Cython 在生成非类时不会填充这些槽cdef。
这些的优点是(对于 C/Cython 类)查找函数__setitem__只涉及几个指针 NULL 检查,然后是 C 函数调用。这也适用于__len__也由插槽定义的PyTypeObject
相比之下,
对于 Python 类调用__setitem__,它使用默认实现来查找字符串的字典"__setitem__"。
cdef对于调用非特殊函数的a 类或 Python 类def,从类/实例字典中查找属性(速度较慢)
请注意,如果要在assetitem中定义常规函数(并从 Cython 调用),则 Cython 会实现自己的快速查找机制。cdef classcpdef
找到属性后,必须调用它。PyTypeObject在从(例如__setitem__和__len__)检索特殊函数的地方cdef class,它们只是 C 函数指针,因此可以直接调用。
对于所有其他情况,PyObject必须评估从属性查找中检索到的内容以查看它是否是可调用的,然后进行调用。
当作为特殊函数__setitem__调用时,PyTypeObject返回值是一个 int,它仅用作错误标志。不需要引用计数或处理 Python 对象。
当__len__从 a 作为特殊函数调用时PyTypeObject,返回类型为 a Py_ssize_t,必须将其转换为 Python 对象,然后在不再需要时将其销毁。
对于普通函数(例如setitem,从 Python 或 Cython 类调用,或__setitem__在 Python 类中定义),返回值是 a PyObject*,必须对其进行适当的引用计数/销毁。
总之,区别实际上在于查找和调用函数的快捷方式,而不是函数的内容是否是 Cythonized。
| 归档时间: |
|
| 查看次数: |
134 次 |
| 最近记录: |