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

Why is str.translate much faster in Python 3.5 compared to Python 3.4?

我试图使用 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)"

Python3.4 程序需要 1.3ms 而 Python3.5 中的相同程序只需要 26.4μs

Python 3.5 相比 Python 3.4 有何改进使其更快?

TL;DR - ISSUE 21118


长篇大论

Josh Rosenberg 发现 str.translate() 函数与 bytes.translate 相比非常慢,他提出了一个 issue,指出:

In Python 3, str.translate() is usually a performance pessimization, not optimization.

为什么 str.translate() 很慢?

str.translate() 非常慢的主要原因是查找曾经在 Python 字典中。

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

现在发生了什么?

第一种方法是添加一个小补丁,translate_writer, However the speed increase was not that pleasing. Soon another patch 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 在下面的 中提到的那样,在 3.5 之前,翻译曾经以相同的方式处理 ASCII 和非 ASCII 情况。然而,从 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