我有一个庞大的Python代码库,我们最近开始用Cython编译。在不对代码进行任何更改的情况下,我预计性能将保持不变,但我们计划在分析后使用Cython特定代码优化较重的计算。但是,编译应用程序的速度急剧下降,似乎是全面的。方法比以前长10%到300%。
我一直在玩测试代码试图找到Cython做得不好的事情,看起来字符串操作就是其中之一。我的问题是,我做错了什么,或者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])
使用
编译Cython模块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代码,用两种方法尝试了各种标志。
答案 0 :(得分:4)
值得一读:Pep 0008>编程建议:
代码的编写应该不会影响Python的其他实现(PyPy,Jython,IronPython,Cython,Psyco等)。
例如,不要依赖CPython为a + = b或a = a + b形式的语句高效实现就地字符串连接。即使在CPython中,这种优化也很脆弱(它只适用于某些类型),并且在不使用引用计数的实现中根本不存在。在库的性能敏感部分,应该使用'.join()形式。这将确保在各种实现中以线性时间进行连接。
参考:https://www.python.org/dev/peps/pep-0008/#programming-recommendations
答案 1 :(得分:2)
该表单的重复字符串连接通常不受欢迎;一些解释器无论如何都要对其进行优化(在已知安全的情况下秘密地分配并允许技术上不可变数据类型的变异),但Cython正试图对某些事情进行硬编码,这使得更难。
真正的答案是"不要一遍又一遍地连接不可变类型。" (到处都是错的,在Cython中更糟糕)。 Cython可能处理得很好的一个非常合理的方法是创建list
个人str
,然后在结尾处调用''.join(listofstr)
以立即生成str
。< / p>
无论如何,你没有给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)