我严重破坏了 Cython,它的性能比纯 Python 还差。为什么?
I've mangled Cython badly, it's performing worse than pure Python. Why?
我是 Python 的新手,完全不了解 运行 C(不幸的是),所以我很难正确理解使用 Cython 的某些方面。
在分析了一个 Python 程序并发现它只是几个循环占用大部分时间后,我决定考虑将它们转储到 Cython 中。最初,我只是让 Cython 按原样解释 Python,结果是(非常好!)~2 倍的速度提升。酷!
从 Python main 开始,我向函数传递了两个二维数组("a" 和 "b")和一个浮点数 "d",它 returns 一个列表,"newlist"。例如:
a =[[12.7, 13.5, 1.0],[23.4, 43.1, 1.0],...]
b =[[0.46,0.95,0],[4.56,0.92,0],...]
d = 0.1
这是原始代码,只是为 Cython 添加了 cdef:
def loop(a, b, d):
cdef int i, j
cdef double x, y
newlist = []
for i in range(len(a)):
if b[i][2] != 1:
for j in range(i+1,len(a)):
if a[i] == a[j] and b[j][2] != 1:
x = b[i][0]+b[j][0]
y = b[i][1]+b[j][1]
b[i][2],b[j][2] = 1,1
if abs(y)/abs(x) > d:
if y > 0: newlist.append([a[i][0],a[i][1],y])
return newlist
在 "pure Python" 中,这个 运行(有几万个循环)在 ~12.5 秒内。在 Cython 中,它在 ~6.3 秒内 运行。已完成接近零的工作,取得了巨大进步!
然而,通过一些阅读,很明显可以做更多的事情,所以我开始尝试应用一些类型更改来让事情进展得更快,遵循 Cython 文档,here(也在评论中引用)。
以下是收集的修改,旨在模仿 Cython 文档:
import numpy as np
cimport numpy as np
DtypeA = np.float
DtypeB = np.int
ctypedef np.float_t DtypeA_t
ctypedef np.int_t DtypeB_t
def loop(np.ndarray[DtypeA_t, ndim=2] A,
np.ndarray[DtypeA_t, ndim=2] B,
np.ndarray[DtypeB_t, ndim=1] C,
float D):
cdef Py_ssize_t i, j
cdef float x, y
cdef np.ndarray[DtypeA_t, ndim=2] NEW_ARRAY = np.zeros((len(C),3), dtype=DtypeA)
for i in range(len(C)):
if C[i] != 1:
for j in range(i+1,len(C)):
if A[i][0]==A[j][0] and A[i][1]==A[j][1] and C[j]!= 1:
x = B[i][0]+B[j][0]
y = B[i][1]+B[j][1]
C[i],C[j] = 1,1
if abs(y)/abs(x) > D:
if y > 0: NEW_ARRAY[i]=([A[i][0],A[i][1],y])
return NEW_ARRAY
除此之外,我将之前的数组 "b" 拆分为两个不同的输入数组 "B" 和 "C",因为 "b" 的每一行包含 2 个 float 元素和一个仅充当标志的整数。所以我删除了标志整数并将它们放在一个单独的一维数组 "C" 中。所以,输入现在看起来像这样:
A =[[12.7, 13.5, 1.0],[23.4, 43.1, 1.0],...]
B =[[0.46,0.95],[4.56,0.92],...]
C =[0,0,...]
D = 0.1
理想情况下,现在输入所有变量应该会快得多(?)...但显然我做错了,因为函数现在在 35.3 秒时出现...比 "pure Python"!!
更糟糕
我到底在搞什么鬼?感谢阅读!
你有没有用cython -a
手动编译过,并检查过逐行注解? (如果是这样,如果您可以 post 它的图像,或者写下它告诉您的内容,那将对我们有所帮助)。用黄色突出显示的行表示源到源转译器输出导致大量使用 CPython API.
的行
例如,您 cdef Py_ssize_t i, j
但没有理由不能是 C 整数。将它们视为 Py_ssize_t
需要开销,如果它们仅用作带边界的简单循环中的索引,您可以轻松保证,没有必要。我不会提出 Py_ssize_t
来试图说一般不要使用它。如果您的情况涉及需要支持索引的 64 位架构或更高精度的整数,那么当然可以使用它。我之所以提到它,是因为像这样的小事情有时会对 when/why 产生意想不到的重大影响,一堆 CPython API 的东西被捆绑到一些你认为会的 Cython 代码中摆脱 CPythonAPI。也许您的代码中一个更好的示例是在循环内使用 Python bool
值和 and
的构造,而不是这些的向量化或 C 级版本。
Cython 中的此类位置通常指的是您不会获得加速并且经常会减速的位置,特别是如果它们与 NumPy 代码混合在一起,由于其对 Cython / 扩展包装器的更紧密优化使用,否则就不必处理 CPython API 开销。您可以让 cython -a
输出指导您添加 C 级类型声明以替换 Python 类型,或者使用 C 级函数(例如来自 C 数学库)而不是 Python 可能需要处理参数的操作,即使是键入时,也可能是任何类型的 Python 对象,以及这涉及的所有许多属性查找和调度调用。
我相信索引符号 b[j][0]
的使用可能会影响 Cython,使其无法在幕后使用快速索引操作。顺便说一句,即使在纯 Python 代码中,这种风格也不是惯用的,可能会导致代码变慢。
尝试在整个过程中使用符号 b[j,0]
,看看它是否会提高您的表现。
显示带有 cython -a
的注释对于优化 Cython 代码确实非常有用。这是一个应该更快的版本,它使用更清晰的内存视图语法,
# cython: boundscheck=False
# cython: cdivision=True
# cython: wraparound=False
import numpy as np
cimport numpy as np
def loop(double [:,::1] A, double [:,::1] B, long [::1] C, float D):
cdef Py_ssize_t i, j, N
cdef float x, y
N = len(C)
cdef double[:,::1] NEW_ARRAY = np.zeros((N,3), dtype='float64')
for i in range(N):
if C[i] != 1:
for j in range(i+1, N):
if (A[i,0]==A[j,0]) & (A[i,1]==A[j,1]) & (C[j] != 1):
x = B[i,0] + B[j,0]
y = B[i,1] + B[j,1]
C[i] = 1
C[j] = 1
if abs(y/x) > D and y >0:
NEW_ARRAY[i,0] = A[i,0]
NEW_ARRAY[i,1] = A[i,1]
NEW_ARRAY[i,2] = y
return NEW_ARRAY.base
我是 Python 的新手,完全不了解 运行 C(不幸的是),所以我很难正确理解使用 Cython 的某些方面。
在分析了一个 Python 程序并发现它只是几个循环占用大部分时间后,我决定考虑将它们转储到 Cython 中。最初,我只是让 Cython 按原样解释 Python,结果是(非常好!)~2 倍的速度提升。酷!
从 Python main 开始,我向函数传递了两个二维数组("a" 和 "b")和一个浮点数 "d",它 returns 一个列表,"newlist"。例如:
a =[[12.7, 13.5, 1.0],[23.4, 43.1, 1.0],...]
b =[[0.46,0.95,0],[4.56,0.92,0],...]
d = 0.1
这是原始代码,只是为 Cython 添加了 cdef:
def loop(a, b, d):
cdef int i, j
cdef double x, y
newlist = []
for i in range(len(a)):
if b[i][2] != 1:
for j in range(i+1,len(a)):
if a[i] == a[j] and b[j][2] != 1:
x = b[i][0]+b[j][0]
y = b[i][1]+b[j][1]
b[i][2],b[j][2] = 1,1
if abs(y)/abs(x) > d:
if y > 0: newlist.append([a[i][0],a[i][1],y])
return newlist
在 "pure Python" 中,这个 运行(有几万个循环)在 ~12.5 秒内。在 Cython 中,它在 ~6.3 秒内 运行。已完成接近零的工作,取得了巨大进步!
然而,通过一些阅读,很明显可以做更多的事情,所以我开始尝试应用一些类型更改来让事情进展得更快,遵循 Cython 文档,here(也在评论中引用)。
以下是收集的修改,旨在模仿 Cython 文档:
import numpy as np
cimport numpy as np
DtypeA = np.float
DtypeB = np.int
ctypedef np.float_t DtypeA_t
ctypedef np.int_t DtypeB_t
def loop(np.ndarray[DtypeA_t, ndim=2] A,
np.ndarray[DtypeA_t, ndim=2] B,
np.ndarray[DtypeB_t, ndim=1] C,
float D):
cdef Py_ssize_t i, j
cdef float x, y
cdef np.ndarray[DtypeA_t, ndim=2] NEW_ARRAY = np.zeros((len(C),3), dtype=DtypeA)
for i in range(len(C)):
if C[i] != 1:
for j in range(i+1,len(C)):
if A[i][0]==A[j][0] and A[i][1]==A[j][1] and C[j]!= 1:
x = B[i][0]+B[j][0]
y = B[i][1]+B[j][1]
C[i],C[j] = 1,1
if abs(y)/abs(x) > D:
if y > 0: NEW_ARRAY[i]=([A[i][0],A[i][1],y])
return NEW_ARRAY
除此之外,我将之前的数组 "b" 拆分为两个不同的输入数组 "B" 和 "C",因为 "b" 的每一行包含 2 个 float 元素和一个仅充当标志的整数。所以我删除了标志整数并将它们放在一个单独的一维数组 "C" 中。所以,输入现在看起来像这样:
A =[[12.7, 13.5, 1.0],[23.4, 43.1, 1.0],...]
B =[[0.46,0.95],[4.56,0.92],...]
C =[0,0,...]
D = 0.1
理想情况下,现在输入所有变量应该会快得多(?)...但显然我做错了,因为函数现在在 35.3 秒时出现...比 "pure Python"!!
更糟糕我到底在搞什么鬼?感谢阅读!
你有没有用cython -a
手动编译过,并检查过逐行注解? (如果是这样,如果您可以 post 它的图像,或者写下它告诉您的内容,那将对我们有所帮助)。用黄色突出显示的行表示源到源转译器输出导致大量使用 CPython API.
例如,您 cdef Py_ssize_t i, j
但没有理由不能是 C 整数。将它们视为 Py_ssize_t
需要开销,如果它们仅用作带边界的简单循环中的索引,您可以轻松保证,没有必要。我不会提出 Py_ssize_t
来试图说一般不要使用它。如果您的情况涉及需要支持索引的 64 位架构或更高精度的整数,那么当然可以使用它。我之所以提到它,是因为像这样的小事情有时会对 when/why 产生意想不到的重大影响,一堆 CPython API 的东西被捆绑到一些你认为会的 Cython 代码中摆脱 CPythonAPI。也许您的代码中一个更好的示例是在循环内使用 Python bool
值和 and
的构造,而不是这些的向量化或 C 级版本。
Cython 中的此类位置通常指的是您不会获得加速并且经常会减速的位置,特别是如果它们与 NumPy 代码混合在一起,由于其对 Cython / 扩展包装器的更紧密优化使用,否则就不必处理 CPython API 开销。您可以让 cython -a
输出指导您添加 C 级类型声明以替换 Python 类型,或者使用 C 级函数(例如来自 C 数学库)而不是 Python 可能需要处理参数的操作,即使是键入时,也可能是任何类型的 Python 对象,以及这涉及的所有许多属性查找和调度调用。
我相信索引符号 b[j][0]
的使用可能会影响 Cython,使其无法在幕后使用快速索引操作。顺便说一句,即使在纯 Python 代码中,这种风格也不是惯用的,可能会导致代码变慢。
尝试在整个过程中使用符号 b[j,0]
,看看它是否会提高您的表现。
显示带有 cython -a
的注释对于优化 Cython 代码确实非常有用。这是一个应该更快的版本,它使用更清晰的内存视图语法,
# cython: boundscheck=False
# cython: cdivision=True
# cython: wraparound=False
import numpy as np
cimport numpy as np
def loop(double [:,::1] A, double [:,::1] B, long [::1] C, float D):
cdef Py_ssize_t i, j, N
cdef float x, y
N = len(C)
cdef double[:,::1] NEW_ARRAY = np.zeros((N,3), dtype='float64')
for i in range(N):
if C[i] != 1:
for j in range(i+1, N):
if (A[i,0]==A[j,0]) & (A[i,1]==A[j,1]) & (C[j] != 1):
x = B[i,0] + B[j,0]
y = B[i,1] + B[j,1]
C[i] = 1
C[j] = 1
if abs(y/x) > D and y >0:
NEW_ARRAY[i,0] = A[i,0]
NEW_ARRAY[i,1] = A[i,1]
NEW_ARRAY[i,2] = y
return NEW_ARRAY.base