为什么这段代码在Cython中比Python运行得更慢?

16

由于性能问题,我开始学习Cython。这段代码是尝试在运输建模(用于规划)领域中实现一些新算法的。

我决定从一个非常简单的函数开始,这个函数我会使用很多次(数亿次),并且绝对可以从性能提升中受益。

我用三种不同的方式实现了这个函数,并对它们进行了相同参数的测试(为了简单起见),每种方法都测试了1000万次:

  • Cython模块中的Cython代码。 运行时间:3.35秒

  • Cython模块中的Python代码。 运行时间:4.88秒

  • 主脚本中的Python代码。 运行时间:2.98秒

    如您所见,Cython代码比Cython模块中的Python代码慢了45%,比主脚本中编写的代码慢了64%。这怎么可能? 我哪里犯了错?

以下是Cython代码:

def BPR2(vol, cap, al, be):
    con=al*pow(vol/cap,be)
    return con


def func (float volume, float capacity,float alfa,float beta):
    cdef float congest
    congest=alfa*pow(volume/capacity,beta)
    return congest

测试的脚本如下:

agora=clock()
for i in range(10000000):
    q=linkdelay.BPR2(10,5,0.15,4)

agora=clock()-agora
print agora

agora=clock()
for i in range(10000000):
    q=linkdelay.func(10,5,0.15,4)
    
agora=clock()-agora
print agora

agora=clock()
for i in range(10000000):
    q=0.15*pow(10/5,4)
    
agora=clock()-agora
print agora

我知道像是超越函数(幂)会比较慢的问题,但我不认为这应该是一个问题。

因为查找函数空间中的函数需要一定的开销,如果我传递一个数组给函数并返回一个数组,这样会提高性能吗?我能够使用Cython编写的函数来返回一个数组吗?

参考资料:

  • Windows 7 64位
  • Python 2.7.3 64位
  • Cython 0.16 64位
  • Windows Visual Studio 2008

1
所以,如果你正在考虑将一个数组传递到函数中,那么你可以矢量化代码,这种情况下,你是否考虑过简单地使用 NumPy 来完成你想要做的事情?毫无疑问,你示例中的函数可以在 NumPy 中轻松实现。 - Henry Gomersall
这只是一个非常琐碎的函数,Cython需要将PyObject*转换为float再进行反向转换,是不是有点过于繁琐了呢?对于如此小的函数来说,似乎开销有些大。 - Voo
1
只是想澄清一下,你的问题是你在调用函数上花费了大部分时间,使用Cython并不能改善这种情况。我建议你重新表达你的问题,不要预设解决方案(Cython)。这样那些想回答的人就会有更多的信息可以使用。提供一个你实际使用代码的小例子会很有用。 - Henry Gomersall
你不需要使用 float 类型数据,而是需要使用 double 类型数据吗? - K. Brafford
正如您所看到的,Cython代码在Cython模块中比Python代码慢了45%。不,您说Cython花费了3.35秒,而在Cython中的Python花费了4.88秒。在我的世界里,这是快了45%。 - Michel Jung
3个回答

3

测试使用了以下工具:

for i in range(10000000):
  func(2.7,2.3,2.4,i)

这里是结果:
cdef float func(float v, float c, float a, float b):
  return a * (v/c) ** b
#=> 0.85

cpdef float func(float v, float c, float a, float b):
  return a * (v/c) ** b
#=> 0.84

def func(v,c,a,b):
  return a * pow(v/c,b)
#=> 3.41

cdef float func(float v, float c, float a, float b):
  return a * pow(v/c, b)
#=> 2.35

为了最高的效率,您需要在C中定义函数,并将返回类型设置为静态。

出于好奇,如果你使用 from libc.math cimport pow,最后两个是否会变得更好? - mgilson
我现在分别得到了3.35和1.35。所以是的。 - Kassym Dorsel
在外部C库中设置静态返回类型所带来的任何时间减少都将被调用开销所淹没。你所有的速度提升只是通过最小化对Python库的调用来实现的。此外,我很好奇Cython是如何使**运算符比libc.mathpow函数更快的。你有没有可能发布输出的C代码? - Henry Gomersall
我用 ** 取代了 pow 函数,速度提升了约30%。我还测试了函数内部的循环(只是重复计算相同参数1000万次),结果发现Cython比编译为Cython的Python快3倍,比纯Python在主脚本中快8倍... - PCamargo
@Henry Gomersall, 您所说的静态类型数组操作是什么意思?我将主要使用Python(特别是利用NumPy的稀疏矩阵实现)进行工作,因此我的数组将是Python数组... - PCamargo
显示剩余4条评论

1

这个函数可以进行优化(在Python和Cython中,删除中间变量可以提高速度):

def func(float volume, float capacity, float alfa,f loat beta):
    return alfa * pow(volume / capacity, beta)

2
这并不能确切地导致所期望的数量级速度增加... - Henry Gomersall
但这会有所帮助。试一下,看看它能提高速度到哪个程度。 - C0deH4cker
5
不会的。这就是过早优化问题的本质。将你的精力投入到算法的改进中。 - Henry Gomersall

1

当Cython速度较慢时,可能是由于类型转换,可能会因缺乏类型注释而加剧。此外,如果在Cython中使用C数据结构,那么比在Cython中使用Python数据结构要快。

我对CPython 2.x(带有和不带有Cython,带有和不带有psyco)、CPython 3.x(带有和不带有Cython)、Pypy和Jython进行了性能比较。在所考虑的微基准测试中,Pypy是迄今为止最快的: http://stromberg.dnsalias.org/~strombrg/backshift/documentation/performance/


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