Python 字符串比较性能不一致
Python string comparison performance is inconsistent
我想检查字符串比较是如何工作的(我想看看它是否是一个字符一个字符,以及它是否在比较之前检查字符串的长度),所以我使用了这段代码:
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',我不知道为什么。
在测量非常小的时间时,您很容易得到不一致的结果。
重复操作多次,效果会比较好,这样差别就大了:
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==s2
和 s1==s3
花费的时间基本相同。 s1==s4
需要更多的时间,因为在等式可以 return False 之前必须比较更多的字符。
顺便说一句,而time.clock
被timeit.default_timer
用来测量
Windows、time.time
上的时间被 timeit.default_timer
用于测量
Unix 上的时间。使用 timeit.default_timer
而不是 time.clock
或 time.time
使您的代码更加跨平台兼容。
你说得对,字符串先比较长度再比较内容(至少在 2.7 中是这样)。 Here 是 string_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
的不完全可靠行为引起的误报,如其他答案和评论。
我想检查字符串比较是如何工作的(我想看看它是否是一个字符一个字符,以及它是否在比较之前检查字符串的长度),所以我使用了这段代码:
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',我不知道为什么。
在测量非常小的时间时,您很容易得到不一致的结果。 重复操作多次,效果会比较好,这样差别就大了:
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==s2
和 s1==s3
花费的时间基本相同。 s1==s4
需要更多的时间,因为在等式可以 return False 之前必须比较更多的字符。
顺便说一句,而time.clock
被timeit.default_timer
用来测量
Windows、time.time
上的时间被 timeit.default_timer
用于测量
Unix 上的时间。使用 timeit.default_timer
而不是 time.clock
或 time.time
使您的代码更加跨平台兼容。
你说得对,字符串先比较长度再比较内容(至少在 2.7 中是这样)。 Here 是 string_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
的不完全可靠行为引起的误报,如其他答案和评论。