为什么 str.strip() 比 str.strip(' ') 快那么多?

33

使用 str.strip 可以通过两种方式分割空格。你可以使用无参数的调用 str.strip(),它默认使用空格作为分隔符,或者使用带有参数的调用 str.strip(' ') 来显式地提供参数。

但是,为什么这些函数在计时时表现如此不同呢?

使用一个有意大量空格的示例字符串:

s = " " * 100 + 'a' + " " * 100

s.strip()s.strip(' ') 的时间分别为:

%timeit s.strip()
The slowest run took 32.74 times longer than the fastest. This could mean that an intermediate result is being cached.
1000000 loops, best of 3: 396 ns per loop

%timeit s.strip(' ')
100000 loops, best of 3: 4.5 µs per loop

strip需要396ns,而strip(' ')需要4.5 μs,在相同条件下,rstriplstrip也存在类似情况。此外,bytes objects似乎也受到影响

这些时间是在Python 3.5.2上执行的,在Python 2.7.1上差异较小。有关str.strip的文档没有提供任何有用信息,那么,为什么会发生这种情况


有趣。你会期望通过使用strip(' ')来节省时间,因为它不需要去除所有的空白字符,只需去除空格字符即可。 - undefined
2个回答

36

简而言之:

这是因为针对两种不同情况存在两个函数,可以在unicode_strip中看到;do_strip_PyUnicodeXStrip,前者执行速度比后者快得多。

函数do_strip用于普通情况下的str.strip(),即没有参数的情况,而do_argstrip(它包装了_PyUnicode_XStrip)用于调用str.strip(arg)时提供参数的情况。


do_argstrip 只检查分隔符,如果它是有效的并且不等于 None(在这种情况下调用do_strip),则调用 _PyUnicode_XStrip

do_strip_PyUnicode_XStrip 遵循相同的逻辑,使用两个计数器,一个为零,另一个为字符串的长度。

使用两个while 循环,第一个计数器递增,直到达到一个不等于分隔符的值,第二个计数器递减,直到满足相同的条件。

区别在于执行当前字符是否不等于分隔符的方式。

对于 do_strip

在字符串中的字符可以表示为ascii 的最常见情况下,会出现额外的小性能提升。

while (i < len) {
    Py_UCS1 ch = data[i];
    if (!_Py_ascii_whitespace[ch])
        break;
    i++;
}
  • 通过访问底层数组,可以快速访问数据中的当前字符:Py_UCS1 ch = data[i];
  • 检查字符是否为空格是通过对名为_Py_ascii_whitespace[ch]的数组进行简单的索引。

因此,简而言之,它相当有效。

如果字符不在ascii范围内,则差异并不那么明显,但它们确实会减缓整体执行速度:

while (i < len) {
    Py_UCS4 ch = PyUnicode_READ(kind, data, i);
    if (!Py_UNICODE_ISSPACE(ch))
        break;
    i++;
}
  • 使用PyUnicode_READ(kind, data, i)来进行访问。
  • 通过Py_UNICODE_ISSPACE(ch)宏(简单地调用另一个宏:Py_ISSPACE)来检查字符是否为空格。

对于_PyUnicodeXStrip

在这种情况下,访问底层数据与前面的情况一样,使用PyUnicode_Read;然而,检查字符是否为空格(或者实际上是我们提供的任何字符)要复杂得多。

while (i < len) {
     Py_UCS4 ch = PyUnicode_READ(kind, data, i);
     if (!BLOOM(sepmask, ch))
         break;
     if (PyUnicode_FindChar(sepobj, ch, 0, seplen, 1) < 0)
         break;
     i++;
}
PyUnicode_FindChar 被使用,虽然效率高,但与数组访问相比要复杂得多且速度较慢。对于字符串中的每个字符,它都会被调用以查看该字符是否包含在我们提供的分隔符中。随着字符串长度的增加,不断调用此函数所引入的开销也会增加。
对于那些感兴趣的人,PyUnicode_FindChar 在进行相当多的检查后,最终会在 stringlib 中调用 find_char,在分隔符的长度小于 10 的情况下,将一直循环直到找到该字符。
除此之外,还需要考虑已经需要调用的其他附加函数。
关于 lstriprstrip,情况类似。存在用于执行剥离模式的标志,即:对于 rstrip,有 RIGHTSTRIP;对于 lstrip,有 LEFTSTRIP;对于 strip,有 BOTHSTRIP。在 do_strip_PyUnicode_XStrip 内部的逻辑是基于标志条件性地执行的。

7

正如@Jims所解释的那样,同样的行为也出现在bytes对象中:

b = bytes(" " * 100 + "a" + " " * 100, encoding='ascii')

b.strip()      # takes 427ns
b.strip(b' ')  # takes 1.2μs

对于bytearray对象,这种情况不会发生,执行split的功能在这种情况下对于两种情况都是相似的。
此外,在Python2中,根据我的时间测量,这种情况也有所适用。

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