Use case
一个简单的函数,用于检查特定字符串是否在另一个字符串中的位置为 3 的倍数(请参阅此处现实世界的例子 https://en.wikipedia.org/wiki/Stop_codon,在 DNA 序列中寻找终止密码子)。
功能
sliding_window
:取长度为 3 的字符串与搜索字符串进行比较,如果相同则向前移动 3 个字符。
incremental_start
:尝试查找搜索字符串,如果找到的位置不是3的倍数,则尝试查找找到的位置之后的下一个位置。
请注意:示例数据只是为了确保每个函数都必须遍历完整的字符串,性能与真实数据或随机数据类似。
Results
-
Python 2.7: 最初的
sliding_window
使用该函数可以将函数提高约 39 倍incremental_start
Windows 10 上的 Python2.7 中。Ubuntu 上的性能改进略有下降,约 34 倍、约 37 倍、约 18 倍(VM、AWS、本机),但仍在同一范围内。
-
Python 3.4:
sliding_window
比 Python2.7 慢(在 Windows 上为 1.8 倍,在所有 Ubuntu 上为 1.4 倍或 1.5 倍),但是incremental_start
在所有 Ubuntu 上性能下降了 4、5、1.7 倍(VM、AWS、本机),而在 Windows 上几乎没有变化。
-
Windows 与 Ubuntu
Python2.7:虚拟化的 Ubuntu 实现这两个功能所需的时间更少(约 20-30%),原生 Ubuntu 的速度慢了大约 25%incremental_start
, while sliding_window
速度提高了 40%。
Python3: the sliding_window
函数完成所需的时间更少 (~50%),而incremental_start
速度变慢约 2-3 倍。
问题
- 是什么导致 Python 2 与 Python 3 在 Linux 和 Windows 上的性能差异?
- 如何预测这种行为并调整代码以获得最佳性能?
Code
import timeit
text = 'ATG' * 10**6
word = 'TAG'
def sliding_window(text, word):
for pos in range(0, len(text), 3):
if text[pos:pos + 3] == word:
return False
return True
def incremental_start(text, word):
start = 0
while start != -1:
start = text.find(word, start + 1)
if start % 3 == 0:
return False
return True
#sliding window
time = timeit.Timer(lambda: sliding_window(text, word), setup='from __main__ import text, word').timeit(number=10)
print('%3.3f' % time)
#incremental start
time = timeit.Timer(lambda: incremental_start(text, word), setup='from __main__ import text, word').timeit(number=500)
print('%3.3f' % time)
Tables
Ubuntu vs Windows VM AWS Native
Python2.7-Increment 79% 73% 126%
Python2.7-Sliding 70% 70% 60%
Python3.4-Increment 307% 346% 201%
Python3.4-Sliding 54% 59% 48%
Py2 vs 3 Windows VM AWS Native
Increment 105% 409% 501% 168%
Sliding 184% 143% 155% 147%
Absolute times in seconds
Win10 Ubuntu AWS Native
Py2.7-Increment 1.759 1.391 1.279 2.215
Py2.7-Sliding 1.361 0.955 0.958 0.823
Py3.4-Increment 1.853 5.692 6.406 3.722
Py3.4-Sliding 2.507 1.365 1.482 1.214
Details
Windows 10:本机 Windows、32 位 Python 3.4.3 或 2.7.9、i5-2500、16GB RAM
Ubuntu虚拟机:14.04,运行在Windows主机上,64位Python 3.4.3,Python 2.7.6,4核,4GB RAM
AWS:14.04、AWS 微实例、64 位 Python 3.4.3、Python 2.7.6
原生 Ubuntu:14.04、64 位 Python 3.4.3、Python 2.7.6、i5-2500、16GB 内存 [与 Win10 机器相同]
Update
正如英加兹所建议的xrange
and bytes
使用Python3.4后,性能略有提高,但在Ubuntu上性能仍然大幅下降。罪魁祸首似乎是find
当 Ubuntu 和 Py3.4 组合时,速度要慢得多(与从源代码编译的 Py3.5 相同)。这似乎与 Linux 风格相关,在 Debian 上 Py2.7 和 Py3.4 表现相同,在 RedHat 上 Py2.7 比 Py3.4 快得多。
为了更好的比较,Py3.4现在在Windows10和Ubuntu上的64位中使用。 Win10上仍然使用Py27。
import timeit, sys
if sys.version_info >= (3,0):
from builtins import range as xrange
def sliding_window(text, word):
for pos in range(0, len(text), 3):
if text[pos:pos + 3] == word:
return False
return True
def xsliding_window(text, word):
for pos in xrange(0, len(text), 3):
if text[pos:pos + 3] == word:
return False
return True
def incremental_start(text, word):
start = 0
while start != -1:
start = text.find(word, start + 1)
if start % 3 == 0:
return False
return True
text = 'aaa' * 10**6
word = 'aaA'
byte_text = b'aaa' * 10**6
byte_word = b'aaA'
time = timeit.Timer(lambda: sliding_window(text, word), setup='from __main__ import text, word').timeit(number=10)
print('Sliding, regular: %3.3f' % time)
time = timeit.Timer(lambda: incremental_start(text, word), setup='from __main__ import text, word').timeit(number=500)
print('Incremental, regular: %3.3f' % time)
time = timeit.Timer(lambda: sliding_window(byte_text, byte_word), setup='from __main__ import byte_text, byte_word').timeit(number=10)
print('Sliding, byte string: %3.3f' % time)
time = timeit.Timer(lambda: incremental_start(byte_text, byte_word), setup='from __main__ import byte_text, byte_word').timeit(number=500)
print('Incremental, bytes: %3.3f' % time)
time = timeit.Timer(lambda: xsliding_window(byte_text, byte_word), setup='from __main__ import byte_text, byte_word').timeit(number=10)
print('Sliding, xrange&bytes: %3.3f' % time)
time = timeit.Timer(lambda: text.find(word), setup='from __main__ import text, word').timeit(number=1000)
print('simple find in string: %3.3f' % time)
Win10-py27 Wi10-py35 VM-py27 VM-py34
1.440 2.674 0.993 1.368
1.864 1.425 1.436 5.711
1.439 2.388 1.048 1.219
1.887 1.405 1.429 5.750
1.332 2.356 0.772 1.224
3.756 2.811 2.818 11.361