【问题标题】:Python string comparison performance is inconsistentPython字符串比较性能不一致
【发布时间】:2015-10-11 07:46:45
【问题描述】:

我想检查字符串比较是如何工作的(我想看看它是否是逐个字符的,以及是否在比较之前检查字符串的长度),所以我使用了以下代码:

s1 = 'abc'
s2 = 'abcd'
s3 = 'dbc'
s4 = 'abd'
t1 = time.clock()
s1==s2
print time.clock() - t1
t2 = time.clock()
s1==s3
print time.clock() - t2
t3 = time.clock()
s1==s4
print time.clock() - t3

当我在非常长的字符串(约 30MB 文本文件)上尝试相同的操作时,它效果很好,我发现它确实执行了长度检查,并且它还逐个字符地比较了字符。 但是当我在短字符串(例如上面代码中的字符串)上尝试过它时,性能结果非常不一致。 任何人都知道为什么他们不一致或我做错了什么? (也许我错了,比较不像我想的那样工作?)

编辑:我也尝试过的一个例子是将不同长度的字符串与特定字符串进行比较。我认为执行时间最长的字符串将是与另一个字符串具有确切长度的字符串,因为其余字符串将落在长度检查中,但它也不一致)。 假设我正在检查的字符串是 'hello',所以我比较了 'a'、'aa'、'aaa' 等等...... 我期待看到最长的检查将是“aaaaa”,但它是“a”,我不知道为什么。

【问题讨论】:

  • “结果非常不一致” - 以什么方式?你期待什么输出,你看到了什么?
  • @TigerhawkT3 我刚刚编辑了帖子以使其更清晰。
  • time.clock 不是对 Python 代码速度进行基准测试的好选择,尤其是在现代多任务操作系统中,在脚本执行时,可能有几十个其他进程可能会占用 CPU 几分之一秒时断时续。 timeit 会更好,因为它可以平均高负载周期,但仍然存在一些固有的不精确性。
  • 90% 的帖子听起来像是平等测试本身产生的结果不一致。我建议对其进行编辑,以明确您所谈论的是性能。也就是说,仅测试几个字符的单个比较必然会产生不一致的性能结果。这就是大多数测试运行多次的原因。
  • 假设您正在使用该语言的 CPython 实现,您可以了解如何执行字符串比较here(在 C 中)。

标签: python string time char compare


【解决方案1】:

您在比较内容之前比较字符串长度是正确的(至少在 2.7 中)。 Herestring_richcompare 的相关部分:

if (op == Py_EQ) {
    /* Supporting Py_NE here as well does not save
       much time, since Py_NE is rarely used.  */
    if (Py_SIZE(a) == Py_SIZE(b)
        && (a->ob_sval[0] == b->ob_sval[0]
        && memcmp(a->ob_sval, b->ob_sval, Py_SIZE(a)) == 0)) {
        result = Py_True;
    } else {
        result = Py_False;
    }
    goto out;
}

简单来说,检查似乎是按顺序排列的:

  • 如果字符串具有相同的内存地址,则它们是相等的。 (上面代码中没有显示)
  • 如果字符串的大小不同,则它们不相等。
  • 如果字符串的第一个字符不同,则它们不相等。
  • 如果字符串具有相同的字符数组,则它们是相等的。

第三次检查似乎不是绝对必要的,但如果手动检查数组内容比调用 memcmp 更快,则可能是一种优化。


如果您的基准测试向您建议比较不同长度的字符串比比较相同长度的字符串要慢,这可能是由 clock 的不完全可靠的行为引起的误报,如其他答案和 cmets 中所述.

【讨论】:

  • 感谢您在结合您的两个答案时,我设法执行了按预期工作的检查。非常感谢你们!
【解决方案2】:

当测量非常小的时间时,您可能会得到不一致的结果。 通过多次重复操作,您将获得更好的结果,以便差异很大:

t1 = time.clock()
for i in range(10**6):
    s1 == s2
t2 = time.clock()

更好的是,使用 timeit 模块来处理重复(以及其他细节 比如关闭垃圾收集)为你:

import timeit

s1 = 'abc'
s2 = 'abcd'
s3 = 'dbc'
s4 = 'abd'
t1 = timeit.timeit('s1==s2', 'from __main__ import s1, s2', number=10**8)
t2 = timeit.timeit('s1==s3', 'from __main__ import s1, s3', number=10**8)
t3 = timeit.timeit('s1==s4', 'from __main__ import s1, s4', number=10**8)
for t in (t1, t2, t3):
    print(t)

产量

2.82305312157
2.83096408844
3.15551590919

因此s1==s2s1==s3 花费的时间基本相同。 s1==s4 需要更多时间,因为在相等性返回 False 之前必须比较更多字符。


顺便说一句,time.clocktimeit.default_timer 用于测量 Windows 上的时间,time.timetimeit.default_timer 用于测量 Unix 上的时间。使用timeit.default_timer 而不是time.clocktime.time 使您的代码更具跨平台兼容性。

【讨论】:

  • 感谢您在结合您的两个答案时,我设法执行了按预期工作的检查。非常感谢你们!
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2010-10-25
  • 2016-04-27
  • 1970-01-01
  • 2016-08-19
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多