【问题标题】:Python str.translate VS str.replacePython str.translate VS str.replace
【发布时间】:2015-09-17 13:06:27
【问题描述】:

为什么在 Pythonreplacetranslate 快约 1.5 倍?

In [188]: s = '1 a  2'

In [189]: s.replace(' ','')
Out[189]: '1a2'

In [190]: s.translate(None,' ')
Out[190]: '1a2'

In [191]: %timeit s.replace(' ','')
1000000 loops, best of 3: 399 ns per loop

In [192]: %timeit s.translate(None,' ')
1000000 loops, best of 3: 614 ns per loop

【问题讨论】:

  • 我猜你不是基于单一测量,对吧?
  • 这表明他在 1000000 次测试中超时。
  • @Brobin 哎呀。很好的收获。
  • 假设 CPython 是开源的,您是否考虑过真正查看实现? translate 可能比 replace 更复杂,因为它做得更多。但是,为什么这种差异很重要?
  • Translate 需要一个表(即使您传递的是空值)。至少,额外的检查(包括我假设的空检查)会增加一些时间。

标签: python


【解决方案1】:

假设 Python 2.7(因为我不得不在没有说明的情况下掷硬币),我们可以在 string.py 中找到 string.translatestring.replace 的源代码:

>>> import inspect
>>> import string
>>> inspect.getsourcefile(string.translate)
'/usr/local/Cellar/python/2.7.9/Frameworks/Python.framework/Versions/2.7/lib/python2.7/string.py'
>>> inspect.getsourcefile(string.replace)
'/usr/local/Cellar/python/2.7.9/Frameworks/Python.framework/Versions/2.7/lib/python2.7/string.py'
>>>

哦,我们不能,as string.py 开头:

"""A collection of string operations (most are no longer used).

Warning: most of the code you see here isn't normally used nowadays.
Beginning with Python 1.6, many of these functions are implemented as
methods on the standard string object.

我赞成你,因为你开始了分析的道路,所以让我们继续这个话题:

from cProfile import run
from string import ascii_letters

s = '1 a  2'

def _replace():
    for x in range(5000000):
        s.replace(' ', '')

def _translate():
    for x in range(5000000):    
        s.translate(None, ' ')

替换:

run("_replace()")
         5000004 function calls in 2.059 seconds

   Ordered by: standard name

   ncalls  tottime  percall  cumtime  percall filename:lineno(function)
        1    0.976    0.976    2.059    2.059 <ipython-input-3-9253b3223cde>:8(_replace)
        1    0.000    0.000    2.059    2.059 <string>:1(<module>)
        1    0.000    0.000    0.000    0.000 {method 'disable' of '_lsprof.Profiler' objects}
  5000000    1.033    0.000    1.033    0.000 {method 'replace' of 'str' objects}
        1    0.050    0.050    0.050    0.050 {range}

翻译:

run("_translate()")

         5000004 function calls in 1.785 seconds

   Ordered by: standard name

   ncalls  tottime  percall  cumtime  percall filename:lineno(function)
        1    0.977    0.977    1.785    1.785 <ipython-input-3-9253b3223cde>:12(_translate)
        1    0.000    0.000    1.785    1.785 <string>:1(<module>)
        1    0.000    0.000    0.000    0.000 {method 'disable' of '_lsprof.Profiler' objects}
  5000000    0.756    0.000    0.756    0.000 {method 'translate' of 'str' objects}
        1    0.052    0.052    0.052    0.052 {range}

我们的函数调用次数是相同的,并不是说函数调用越多就意味着运行速度越慢,但它通常是一个很好的查看位置。有趣的是translate 在我的机器上跑得比replace 快!考虑一下不孤立地测试更改的乐趣——这并不重要,因为我们只关心能够说明为什么可能会有所不同。

无论如何,我们至少现在知道可能存在性能差异,并且在评估字符串对象的方法时确实存在(参见tottime)。 translate __docstring__ 表明有一个翻译表在起作用,而替换只提到旧到新的子字符串替换。

让我们向我们的老朋友dis 寻求提示:

from dis import dis

替换:

def dis_replace():
    '1 a  2'.replace(' ', '')

dis(dis_replace)


dis("'1 a  2'.replace(' ', '')")

  3           0 LOAD_CONST               1 ('1 a  2')
              3 LOAD_ATTR                0 (replace)
              6 LOAD_CONST               2 (' ')
              9 LOAD_CONST               3 ('')
             12 CALL_FUNCTION            2
             15 POP_TOP             
             16 LOAD_CONST               0 (None)
             19 RETURN_VALUE        

还有translate,对我来说跑得更快:

def dis_translate():
    '1 a  2'.translate(None, ' ')
dis(dis_translate)    


  2           0 LOAD_CONST               1 ('1 a  2')
              3 LOAD_ATTR                0 (translate)
              6 LOAD_CONST               0 (None)
              9 LOAD_CONST               2 (' ')
             12 CALL_FUNCTION            2
             15 POP_TOP             
             16 LOAD_CONST               0 (None)
             19 RETURN_VALUE        

不幸的是,两者看起来与dis 相同,这意味着我们应该在这里开始查找字符串的C 源代码(通过转到我现在使用的Python 版本的python 源代码找到)]( https://hg.python.org/cpython/file/a887ce8611d2/Objects/stringobject.c)。

这是source for translate
如果通过 cmets,您可以看到有多个 replace 函数定义行,基于输入的长度。

我们的子字符串替换选项是:

replace_substring_in_place

/* len(self)>=1, len(from)==len(to)>=2, maxcount>=1 */
Py_LOCAL(PyStringObject *)
replace_substring_in_place(PyStringObject *self,

replace_substring:

/* len(self)>=1, len(from)>=2, len(to)>=2, maxcount>=1 */
Py_LOCAL(PyStringObject *)
replace_substring(PyStringObject *self,

replace_delete_single_character:

/* Special case for deleting a single character */
/* len(self)>=1, len(from)==1, to="", maxcount>=1 */
Py_LOCAL(PyStringObject *)
replace_delete_single_character(PyStringObject *self,
                                char from_c, Py_ssize_t maxcount)

'1 a 2'.replace(' ', '') 是一个 len(self)==6,将 1 个字符替换为一个空字符串,使其成为 replace_delete_single_character

您可以自己查看函数体,但答案是“对于此特定输入,C 函数体在 replace_delete_single_character 中的运行速度比 string_translate 快。

感谢您提出这个问题。

【讨论】:

  • 感谢您对分析此问题的见解和指导。特别是dis 模块,我不知道。我接受你的回答,因为没什么好说的。对我来说,挖掘 Python 的源代码就足够了。再次感谢您。
  • @NarūnasK 嘿,真的,非常感谢您提出这个问题——它非常复杂。我不知道replace 被分派到不同的功能。我认为向您介绍一下如何深入了解 Python 正在做什么会很有帮助。实际上,我为没有通过 C 函数更“深入”而感到难过,但我的午休时间结束了。无论如何,干杯!
  • @NarūnasK 请重新阅读我的回答。我错误地使用了dis,并清理了答案以使其更加连贯。
【解决方案2】:

随着 N 和 M 的增加,翻译可能会更快,其中 N 是唯一字符替换映射的数量,M 是正在翻译的字符串的长度。

import random
import string
import timeit
import re

def do_translation(N,M):
    trans_map = random.sample(string.ascii_lowercase,N),random.sample(string.ascii_lowercase,N)
    trans_tab = string.maketrans(*map("".join,trans_map))
    s = "".join(random.choice(string.ascii_lowercase) for _ in range(M))
    return s.translate(trans_tab)

def do_resub(N,M):
    trans_map = random.sample(string.ascii_lowercase,N),random.sample(string.ascii_lowercase,N)
    trans_tab = dict(zip(*trans_map))
    s = "".join(random.choice(string.ascii_lowercase) for _ in range(M))
    return re.sub("([%s])"%("".join(trans_map[0]),),lambda m:trans_tab.get(m.group(0),m.group(0)),s)

def do_replace(N,M):
    trans_map = random.sample(string.ascii_lowercase,N),random.sample(string.ascii_lowercase,N)
    s = "".join(random.choice(string.ascii_lowercase) for _ in range(M))
    for k,v in zip(*trans_map):
       s = s.replace(k,v)
    return s


data = {}
for i in range(2,20,2):
    for j in range(10,200,10):
        data[(i,j)] = {
            "translate":timeit.timeit("do_translation(%s,%s)"%(i,j),"from __main__ import do_translation,string,random",number=100),
            "re.sub":timeit.timeit("do_resub(%s,%s)"%(i,j),"from __main__ import do_resub,re,random",number=100),
            "replace":timeit.timeit("do_replace(%s,%s)"%(i,j),"from __main__ import do_replace,random",number=100)}

print data

将向您展示几种不同的时间...包括在其中几种情况下翻译可能更快(我考虑在这里添加一些情节...但我已经在这个问题上投入了比我真正应该拥有的时间更多的时间:P)

【讨论】:

  • 为什么随着输入长度的增加翻译变得更快? dis 中有什么东西可以说明问题吗?
猜你喜欢
  • 2013-10-25
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2016-03-21
  • 2017-06-17
  • 2018-09-11
  • 2023-03-07
相关资源
最近更新 更多