【问题标题】:Why is it faster to compare strings that match than strings that do not? [duplicate]为什么比较匹配的字符串比比较不匹配的字符串更快? [复制]
【发布时间】:2022-03-30 19:58:45
【问题描述】:

这里有两个测量:

timeit.timeit(\"toto\"==\"1234\"\', number=100000000)
1.8320042459999968
timeit.timeit(\"toto\"==\"toto\"\', number=100000000)
1.4517491540000265

如您所见,比较两个匹配的字符串比比较两个大小相同但不匹配的字符串要快。 这很令人不安:在字符串比较期间,我认为 Python 正在逐个字符地测试字符串,因此 \"toto\"==\"toto\" 的测试时间应该比 \"toto\"==\"1234\" 长,因为它需要针对不匹配比较进行四次测试。也许比较是基于哈希的,但在这种情况下,两个比较的时间应该相同。

为什么?

  • 字符串实习可能吗?
  • 检查\"toto\" is \"toto\" 的值。很可能同一语句中的两个相同字符串文字被编译为同一个字符串对象。我想如果你的琴弦是用不同的方式制作的,你会得到不同的结果。
  • @RiccardoBucco \"small integers\"(从 -5 到 255 IIRC)实际上是预先记忆的,它们总是会从缓存中获取。所以对他们进行身份检查也很有意义。
  • @RiccardoBucco 是的,但是您具有相同身份的原因是缓存了小整数(在cpython中,作为实现细节)。浮点数没有这样的缓存,因此相同文字的两个实例是不同的对象。并且因为遇到相同的浮点数(相同的对象,不同的值)的可能性很低(因为它们没有被缓存)cpython 不会优化这种比较。
  • \"在字符串比较期间,我认为 python 正在逐个字符地测试字符串\"- 我真诚地怀疑任何体面的编程语言都使用天真的 for 循环进行字符串比较。 Python 肯定不会,it uses memcmp,其中may use SIMD instructions to compare many bytes at a time,以及其他优化。

标签: python performance comparison


【解决方案1】:

结合我的评论和@khelwood 的评论:

TL;博士:
在分析两个比较的字节码时,它显示'time''time' 字符串被分配给同一个对象。因此,一个前期身份检查(在 C 级别)是提高比较速度的原因。

相同对象分配的原因是,作为实施细节, CPython 实习生字符串仅包含“名称字符”(即字母和下划线字符)。这将启用对象的身份检查。


字节码:

import dis

In [24]: dis.dis("'time'=='time'")
  1           0 LOAD_CONST               0 ('time')  # <-- same object (0)
              2 LOAD_CONST               0 ('time')  # <-- same object (0)
              4 COMPARE_OP               2 (==)
              6 RETURN_VALUE

In [25]: dis.dis("'time'=='1234'")
  1           0 LOAD_CONST               0 ('time')  # <-- different object (0)
              2 LOAD_CONST               1 ('1234')  # <-- different object (1)
              4 COMPARE_OP               2 (==)
              6 RETURN_VALUE

分配时间:

在时间测试中使用分配也可以看到“加速”。将两个变量分配(和比较)到同一个字符串,比将两个变量分配(和比较)到不同的字符串要快。进一步支持假设,底层逻辑正在执行对象比较。这在下一节中得到证实。

In [26]: timeit.timeit("x='time'; y='time'; x==y", number=1000000)
Out[26]: 0.0745926329982467

In [27]: timeit.timeit("x='time'; y='1234'; x==y", number=1000000)
Out[27]: 0.10328884399496019

Python源代码:

正如@mkrieger1 和@Masklinn 在他们的cmets 中提供的那样,source code 用于unicodeobject.c 首先执行指针比较,如果True 立即返回。

int
_PyUnicode_Equal(PyObject *str1, PyObject *str2)
{
    assert(PyUnicode_CheckExact(str1));
    assert(PyUnicode_CheckExact(str2));
    if (str1 == str2) {                  // <-- Here
        return 1;
    }
    if (PyUnicode_READY(str1) || PyUnicode_READY(str2)) {
        return -1;
    }
    return unicode_compare_eq(str1, str2);
}

附录:

  • Reference answer 很好地说明了如何读取反汇编的字节码输出。由@Delgan 提供
  • Reference answer 很好地描述了 CPython 的字符串实习。由@ShadowRanger 提供

【讨论】:

  • 如果两个对象代表同一个对象,为什么它们的比较会更快?比较运算符是如何实现的?
  • 对于字符串,它在这里实现:github.com/python/cpython/blob/main/Objects/… 正如预期的那样,它首先检查身份并提前返回。
  • @RiccardoBucco 因为平等检查通常以身份检查,因为它执行起来非常便宜,但如果它允许您绕过“结构”相等检查,则非常有效。您可以在_PyUnicode_Equal 中看到这一点。第 11139 到 11141 行是 C 级相等检查,这意味着它比较指针,这在 CPython 中是身份比较(因为两个对象不能重叠,因此不能具有相同的指针)。
  • @mkrieger1 - 正是我想要的,谢谢。将包括在答案中。
  • @YanickSalzmann CPython 当前缓存(实习生)仅包含单词字符的字符串。见stackoverflow.com/questions/42684966/are-strings-cached
【解决方案2】:

它不是总是更快地比较匹配的字符串。相反,比较共享相同 id 的字符串总是更快。证明身份确实是这种行为的原因(正如@S3DEV 出色地解释的那样)是这个:

>>> x = 'toto'
>>> y = 'toto'
>>> z = 'totoo'[:-1]
>>> w = 'abcd'
>>> x == y
True
>>> x == z
True
>>> x == w
False
>>> id(x) == id(y)
True
>>> id(x) == id(z)
False
>>> id(x) == id(w)
False
>>> timeit.timeit('x==y', number=100000000, globals={'x': x, 'y': y})
3.893762200000083
>>> timeit.timeit('x==z', number=100000000, globals={'x': x, 'z': z})
4.205321462000029
>>> timeit.timeit('x==w', number=100000000, globals={'x': x, 'w': w})
4.15288594499998

比较具有相同 id 的对象总是更快(您可以从示例中注意到,xz 之间的比较比 xy 之间的比较慢,这是因为 xz 不共享相同的ID)。

【讨论】:

  • 仅供参考,直接测试“它们是同一个对象吗?”是x is yid(x) == id(y) 确实得到了相同的结果,但它首先做了一些拇指操作以使 int 对象进行比较,其中 x is y 只是直接比较内存地址而不包装它。
猜你喜欢
  • 1970-01-01
  • 2012-10-12
  • 1970-01-01
  • 2011-06-21
  • 2012-02-09
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多