为什么 cffi 比 numpy 快得多?

15

我一直在尝试用Python编写cffi模块,并且它们的速度让我怀疑我是否正确地使用了标准的Python。这让我想完全切换到C!但实际上,有一些很棒的Python库我永远无法在C中重新实现,所以这更多是假设。

这个例子展示了在Python中使用numpy数组进行求和的函数,与使用c函数相比较慢的情况。有没有更快的Pythonic方式计算numpy数组的总和?

def cast_matrix(matrix, ffi):
    ap = ffi.new("double* [%d]" % (matrix.shape[0]))
    ptr = ffi.cast("double *", matrix.ctypes.data)
    for i in range(matrix.shape[0]):
        ap[i] = ptr + i*matrix.shape[1]                                                                
    return ap 

ffi = FFI()
ffi.cdef("""
double sum(double**, int, int);
""")
C = ffi.verify("""
double sum(double** matrix,int x, int y){
    int i, j; 
    double sum = 0.0;
    for (i=0; i<x; i++){
        for (j=0; j<y; j++){
            sum = sum + matrix[i][j];
        }
    }
    return(sum);
}
""")
m = np.ones(shape=(10,10))
print 'numpy says', m.sum()

m_p = cast_matrix(m, ffi)

sm = C.sum(m_p, m.shape[0], m.shape[1])
print 'cffi says', sm

只为展示函数的工作原理:

numpy says 100.0
cffi says 100.0

现在,如果我计时这个简单的函数,我发现numpy非常慢!我是否正确地使用了numpy?在Python中有更快的计算总和的方法吗?

import time
n = 1000000

t0 = time.time()
for i in range(n): C.sum(m_p, m.shape[0], m.shape[1])
t1 = time.time()

print 'cffi', t1-t0

t0 = time.time()
for i in range(n): m.sum()
t1 = time.time()

print 'numpy', t1-t0

时代:

cffi 0.818415880203
numpy 5.61657714844

1
使用 timeit 模块进行基准测试。如果您已经安装了 ipython,请尝试 %timeit np.sum(np.sum(m)) %timeit np.matrix.sum(x),否则可能会出现垃圾回收等问题(参见 https://docs.python.org/2/library/timeit.html#timeit.Timer.timeit)。 - Fredrik Pihl
可能的情况是由于 Python 的开销,如果使用较大的数组,比如 1E3x1E3,并减少循环次数,就会看到更加可比较的时间。 - Daniel
1个回答

15
Numpy比C慢有两个原因:Python开销(可能类似于cffi)和通用性。Numpy旨在处理各种不同数据类型的任意维度数组。您使用cffi创建了一个2D浮点数数组的示例。成本是编写多行代码与.sum()相比,只节省不到5微秒的6个字符。(但是,您已经知道这一点)。我只想强调CPU时间很便宜,比开发人员的时间便宜得多。
现在,如果您想坚持使用Numpy,并且想获得更好的性能,则最佳选择是使用Bottleneck。他们提供了一些针对1和2D浮点和双精度数组进行优化的函数,速度非常快。在您的情况下,快16倍,将执行时间放在0.35左右,或者比cffi快大约两倍。
对于Bottleneck没有的其他功能,您可以使用Cython。它可以帮助您使用更pythonic的语法编写C代码。或者,如果您愿意,逐渐将Python转换为C,直到满意为止。

2
请注意,如果您直接使用bottleneck的专用函数,则相对于Numpy的加速可高达约25倍。你的cffi现在在哪里? :) - Davidmh
除了处理nan的操作外,这似乎并不比numpy的sum函数快多少。关键在于通过预先选择底层C函数来避免Python开销。 - Daniel
没有NaN,我的速度提高了16到25倍。Numpy是否公开了这些专门的函数?你总是可以从C API中调用它们,但需要通过Cython进行。 - Davidmh
1
另外,也可以看看 Numba。 - meawoppl

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接