Cython 字符串连接超慢;它还有什么不好的地方?
Cython string concatenation is super slow; what else does it do poorly?
我有一个很大的 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 代码,并尝试使用两者的各种标志。
这种形式的重复字符串连接通常是不受欢迎的;一些解释器无论如何都会对其进行优化(在已知安全的情况下秘密过度分配并允许技术上不可变的数据类型发生变化),但 Cython 正试图对某些东西进行硬编码,这使得这变得更加困难。
真正的答案是"Don't concatenate immutable types over and over."(到处都是错误的,在Cython中更糟)。 Cython 可能处理得很好的一个完全合理的方法是制作一个 list
个人 str
,然后在最后调用 ''.join(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 > 编程建议:
Code should be written in a way that does not disadvantage other implementations of Python (PyPy, Jython, IronPython, Cython, Psyco, and such).
For example, do not rely on CPython's efficient implementation of in-place string concatenation for statements in the form a += b or a = a + b . This optimization is fragile even in CPython (it only works for some types) and isn't present at all in implementations that don't use refcounting. In performance sensitive parts of the library, the ''.join() form should be used instead. This will ensure that concatenation occurs in linear time across various implementations.
参考:https://www.python.org/dev/peps/pep-0008/#programming-recommendations
我有一个很大的 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 代码,并尝试使用两者的各种标志。
这种形式的重复字符串连接通常是不受欢迎的;一些解释器无论如何都会对其进行优化(在已知安全的情况下秘密过度分配并允许技术上不可变的数据类型发生变化),但 Cython 正试图对某些东西进行硬编码,这使得这变得更加困难。
真正的答案是"Don't concatenate immutable types over and over."(到处都是错误的,在Cython中更糟)。 Cython 可能处理得很好的一个完全合理的方法是制作一个 list
个人 str
,然后在最后调用 ''.join(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 > 编程建议:
Code should be written in a way that does not disadvantage other implementations of Python (PyPy, Jython, IronPython, Cython, Psyco, and such).
For example, do not rely on CPython's efficient implementation of in-place string concatenation for statements in the form a += b or a = a + b . This optimization is fragile even in CPython (it only works for some types) and isn't present at all in implementations that don't use refcounting. In performance sensitive parts of the library, the ''.join() form should be used instead. This will ensure that concatenation occurs in linear time across various implementations.
参考:https://www.python.org/dev/peps/pep-0008/#programming-recommendations