为什么在Python 3.5中,str.translate比Python 3.4快得多?

116
我尝试使用Python 3.4中的text.translate()方法从给定字符串中删除不需要的字符。
最小化代码如下:
import sys 
s = 'abcde12345@#@$#%$'
mapper = dict.fromkeys(i for i in range(sys.maxunicode) if chr(i) in '@#$')
print(s.translate(mapper))

它按预期工作。然而,当在Python 3.4和Python 3.5中执行相同的程序时,会有很大的差异。

计算时间的代码为:

python3 -m timeit -s "import sys;s = 'abcde12345@#@$#%$'*1000 ; mapper = dict.fromkeys(i for i in range(sys.maxunicode) if chr(i) in '@#$'); "   "s.translate(mapper)"

Python 3.5相比Python 3.4运行更快,前者仅需26.4μs,而后者需要1.3ms。那么Python 3.5有哪些改进使其速度更快呢?


11
谈到性能,使用以下代码生成映射表会更好:dict.fromkeys(ord(c) for c in '@#$')。这将为特定字符创建字典键,而不是像以前那样在循环中每次创建一个新的字典。 - Thomas K
1
@ThomasK 我发现这样做有很大的区别。是的,你的方法更好。 - Bhargav Rao
你是不是指50倍的速度? - assylias
@assylias 我做了1300-26.4,然后除以1300。我得到了近95%,所以我写道:)实际上比原来快50倍以上...但是我的计算有误吗?我在数学方面有点弱。我很快就会学习数学的。 :) - Bhargav Rao
3
你应该这样做:将 26 ÷ 1300 = 2%,这意味着更快的版本所需时间仅为较慢版本的 2%,也就是说速度快了 50 倍。 - assylias
1个回答

152

TL;DR - 问题 21118


长篇故事

Josh Rosenberg 发现 str.translate() 函数比 bytes.translate() 函数慢得多,他提出了一个问题,指出:

在 Python 3 中,str.translate() 通常是性能降低而非优化。

str.translate() 为什么慢?

str.translate() 函数之所以非常慢,主要原因是查找过程以前是在 Python 字典中进行的。

maketrans 的使用使问题变得更糟。使用类似bytes 的方法构建了一个包含 256 个项的 C 数组,以便进行快速表查找。因此,使用较高级别的 Python dict 使得 Python 3.4 中的 str.translate() 非常慢。

现在发生了什么?

第一种方法是添加一个小补丁,translate_writer,但速度提升并不理想。很快就测试了另一个补丁 fast_translate,它产生了非常好的结果,速度提高了多达 55%。

从文件中可以看出主要的更改是将 Python 字典查找改为 C 级别的查找。

现在速度几乎与 bytes 相同。

                                unpatched           patched

str.translate                   4.55125927699919    0.7898181750006188
str.translate from bytes trans  1.8910855210015143  0.779950579000797

需要注意的是,性能增强仅在ASCII字符串中才明显。正如J.F.Sebastian在下面的评论中提到的,在Python 3.5之前,translate在ASCII和非ASCII情况下的工作方式是相同的。但是,从Python 3.5开始,ASCII情况要快得多。

以前,ASCII和非ASCII几乎相同,但现在我们可以看到性能有了很大改善。

这可以从这个回答中看到,性能的提升从71.6μs到2.33μs。

以下代码演示了这一点

python3.5 -m timeit -s "text = 'mJssissippi'*100; d=dict(J='i')" "text.translate(d)"
100000 loops, best of 3: 2.3 usec per loop
python3.5 -m timeit -s "text = 'm\U0001F602ssissippi'*100; d={'\U0001F602': 'i'}" "text.translate(d)"
10000 loops, best of 3: 117 usec per loop

python3 -m timeit -s "text = 'm\U0001F602ssissippi'*100; d={'\U0001F602': 'i'}" "text.translate(d)"
10000 loops, best of 3: 91.2 usec per loop
python3 -m timeit -s "text = 'mJssissippi'*100; d=dict(J='i')" "text.translate(d)"
10000 loops, best of 3: 101 usec per loop

结果的表格:

         Python 3.4    Python 3.5  
Ascii     91.2          2.3 
Unicode   101           117

13
这是其中一个提交记录:https://github.com/python/cpython/commit/87056cb061ede3c30952c43970b41cafa112d3bf - filmor
注意:ASCII和非ASCII情况下的性能可能会有显著差异。这不是关于“55%”的问题:正如你的答案所示,加速可以达到“1000s%” - jfs
@J.F. Oh,我现在明白了。我在3.4和3.5上运行了您的代码。对于非ASCII字符,我发现Py3.4更快。这是巧合吗?结果请参见http://dpaste.com/15FKSDQ。 - Bhargav Rao
在3.5版本之前,Unicode的.translate()方法中ASCII和非ASCII情况可能是相同的,即Python 3.5只有ASCII情况更快(在那里你不需要使用bytes.translate()来提高性能)。 - jfs
@BhargavRao 哦,没问题。对于百分比变化,始终要除以_原始_值,而不是新值。在这种情况下,(91.2 - 2.3) / 91.2 x 100% = 97.5% 的改进。 - David Z
显示剩余7条评论

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