Python Cython字符串连接速度非常慢;它还能做什么?
我有一个庞大的Python代码库,我们最近开始使用Cython进行编译。在不对代码进行任何更改的情况下,我希望性能保持不变,但我们计划在评测之后使用Cython特定的代码优化更繁重的计算。然而,编译后的应用程序的速度急剧下降,而且似乎是全面的。这些方法比以前花费了10%到300%的时间 我一直在玩弄测试代码,试图找出Cython做得不好的地方,而字符串操作似乎就是其中之一。我的问题是,我是做错了什么,还是Cython在某些方面真的很糟糕?你能帮我理解为什么这件事这么糟糕,还有什么事Cython可能做得很糟糕 编辑:让我试着澄清一下。我意识到这种类型的字符串连接非常糟糕;我只是注意到它有一个巨大的速度差,所以我张贴了它(可能是个坏主意)。代码库中没有这种糟糕的代码,但速度仍然非常缓慢,我希望能找到关于Cython处理糟糕的构造类型的指针,这样我就可以知道应该在哪里查找。我尝试过分析,但没有特别的帮助 作为参考,这里是我的字符串操作测试代码。我意识到下面的代码是可怕的和无用的,但我仍然对速度的差异感到震惊Python Cython字符串连接速度非常慢;它还能做什么?,python,performance,cython,Python,Performance,Cython,我有一个庞大的Python代码库,我们最近开始使用Cython进行编译。在不对代码进行任何更改的情况下,我希望性能保持不变,但我们计划在评测之后使用Cython特定的代码优化更繁重的计算。然而,编译后的应用程序的速度急剧下降,而且似乎是全面的。这些方法比以前花费了10%到300%的时间 我一直在玩弄测试代码,试图找出Cython做得不好的地方,而字符串操作似乎就是其中之一。我的问题是,我是做错了什么,还是Cython在某些方面真的很糟糕?你能帮我理解为什么这件事这么糟糕,还有什么事Cython可
# pyCode.py
def str1():
val = ""
for i in xrange(100000):
val = str(i)
def str2():
val = ""
for i in xrange(100000):
val += 'a'
def str3():
val = ""
for i in xrange(100000):
val += str(i)
定时码
# compare.py
import timeit
pyTimes = {}
cyTimes = {}
# STR1
number=10
setup = "import pyCode"
stmt = "pyCode.str1()"
pyTimes['str1'] = timeit.timeit(stmt=stmt, setup=setup, number=number)
setup = "import cyCode"
stmt = "cyCode.str1()"
cyTimes['str1'] = timeit.timeit(stmt=stmt, setup=setup, number=number)
# STR2
setup = "import pyCode"
stmt = "pyCode.str2()"
pyTimes['str2'] = timeit.timeit(stmt=stmt, setup=setup, number=number)
setup = "import cyCode"
stmt = "cyCode.str2()"
cyTimes['str2'] = timeit.timeit(stmt=stmt, setup=setup, number=number)
# STR3
setup = "import pyCode"
stmt = "pyCode.str3()"
pyTimes['str3'] = timeit.timeit(stmt=stmt, setup=setup, number=number)
setup = "import cyCode"
stmt = "cyCode.str3()"
cyTimes['str3'] = timeit.timeit(stmt=stmt, setup=setup, number=number)
for funcName in sorted(pyTimes.viewkeys()):
print "PY {} took {}s".format(funcName, pyTimes[funcName])
print "CY {} took {}s".format(funcName, cyTimes[funcName])
使用
cp pyCode.py cyCode.py
cython cyCode.py
gcc -O2 -fPIC -shared -I$PYTHONHOME/include/python2.7 \
-fno-strict-aliasing -fno-strict-overflow -o cyCode.so cyCode.c
结果计时
> python compare.py
PY str1 took 0.1610019207s
CY str1 took 0.104282140732s
PY str2 took 0.0739600658417s
CY str2 took 2.34380102158s
PY str3 took 0.224936962128s
CY str3 took 21.6859738827s
作为参考,我已经用Cython 0.19.1和0.23.4尝试过了。我用gcc 4.8.2和icc 14.0.2编译了C代码,尝试了使用这两种格式的各种标志。通常不赞成这种格式的重复字符串串联;一些解释器为此进行了优化(秘密地过度分配,并允许在已知安全的情况下对技术上不可变的数据类型进行变异),但Cython正试图对某些事情进行硬编码,这使得这变得更加困难 真正的答案是“不要一次又一次地连接不可变的类型。”(这在任何地方都是错误的,在Cython中更糟糕)。Cython可能会处理fine的一个完全合理的方法是为个人
str
创建一个列表,然后调用''。最后加入(listofstr)
,立即创建str
无论如何,你不会给Cython任何打字信息,所以速度不会太快。试着用简单的东西来帮助它,那里的加速可能会弥补其他地方的损失。例如,cdef
您的循环变量并使用''。join
可能有助于:
cpdef str2():
cdef int i
val = []
for i in xrange(100000): # Maybe range; Cython docs aren't clear if xrange optimized
val.append('a')
val = ''.join(val)
值得一读:Pep 0008>编程建议:
代码的编写方式应该不会对其他Python实现(PyPy、Jython、IronPython、Cython、Psyco等)造成不利影响
例如,对于形式为a+=b或a=a+b的语句,不要依赖于CPython对就地字符串连接的高效实现。这种优化即使在CPython中也很脆弱(它只适用于某些类型),并且在不使用refcounting的实现中根本不存在。在库的性能敏感部分中,应改为使用“”。join()形式。这将确保在各种实现中以线性时间进行连接
参考:cython
代码必须进行Python函数调用,以格式化整数并创建新字符串。它可以将迭代转换为C,但这只是操作的一小部分。谢谢你的回答。我意识到这种类型的连接是一种糟糕的做法,但我的印象是,如果没有优化,Cython代码将吐出与CPython基本相同的代码,但显然不是。但是我们的代码库相当成熟,所以我会震惊于在任何地方发现这种类型的代码;你有没有指向其他CPython可能正在优化但Cython不会优化的东西的指针?@rpmcnally:列举这些东西真的很难。Cython优化和CPython优化在某些方面几乎是对立的;Cython从使用大量低级“原语”(例如在范围内循环并索引列表)中获益,因为它很容易转换为C;相比之下,CPython直接迭代列表
或在需要列表时使用枚举
要快得多。基本上,Cython在编写类似C的Python代码时是最快的,使用类似C的假设(其中一个假设是字符串连接很糟糕)。问题是,分析显示,经济全面放缓;几乎所有的东西都慢了。我想我必须想出一个更好的方法来分析这段代码。那么,“我问了一个糟糕的问题”的适当礼仪是什么呢?只需删除它或选择最接近答案的内容?@rpmcnally我会保留问题,并(可选)选择最能回答有关字符串连接的原始问题的答案。更一般的问题是“Cython与CPython相比做得差吗?”可能更难得到明确的答案,并且可能会因为“过于宽泛”而结束,因为知道CPython专门为此进行了优化。事实上,将第三个示例更改为'val=str(i)+val'会使CPython比Cython花费更长的时间(~24s)。所以,也许真正的问题是,我如何知道CPython优化了哪些,而其他实现可能没有?我确信字符串连接不是我们代码库中的真正问题。有关优化发生在何处的好奇,请参阅CPython解释程序源:。注意“pyunicode”的特殊情况检查(至少在python3中!)。相反,Cython只做PyNumber\u inpeaceadd
,如果您想知道CPython在哪里做Cython没有做的优化,那么在该文件中搜索\u CheckExact
可能是一个很好的开始,尽管它可能有点乏味。另一个主要原因显然是坦率的