一些基准测试具有随机相等的字符串/百万个字符的字节(
on TIO
Python 3.8预发行版,但我在3.10.2中获得了类似的时间):
186.88 us s.encode()
187.39 us s.encode("utf-8")
183.85 us s.encode("ascii")
94.62 us b.decode()
94.27 us b.decode("utf-8")
137.91 us b.decode("ascii")
79.93 us s == s2
82.69 us b == b2
182.72 us s + "a"
177.06 us b + b"a"
0.08 us len(s)
0.07 us len(b)
1.14 us s[:1000].encode()
0.97 us b[:1000].decode()
2.06 us s[::1000].encode()
1.45 us b[::1000].decode()
1.91 us hash(s)
1.56 us hash(b)
508.62 us hash(s2)
546.00 us hash(b2)
2.85 us str(s)
9142.59 us str(b)
13541.64 us repr(s)
9100.34 us repr(b)
基于此的想法:
-
我想对于更简单的代码,也许我们可以申请
str
或
repr
然后以某种方式比较得到的字符串(如删除后
b
前缀),但基准测试表明这将非常缓慢。
-
获取长度非常便宜,所以我会先比较一下。回来
False
如果不同,则继续。
-
如果你已经对它们进行了散列运算,或者以后还要进行散列运算,那么你可以比较散列(并返回
错误
如果不同则继续)。看见
ASCII str / bytes hash collision
为什么相等的ASCII字符串和ASCII字节具有相同的散列。(但我不确定它是否有语言保证,所以它可能不安全,我不确定)。请注意,第一次哈希很慢(请参阅哈希时间
s2
/
b2
)但是对存储的散列的后续查找是快速的(请参阅有关散列的时间
s
/
b
)。
-
解码似乎比编码快,所以应该这样做。
-
只有当类型不同时才解码(一个是字符串,一个是字节),否则只使用
==
。
-
如果第一个字节已经不匹配,那么解码一百万个字节是浪费的。因此,解码/比较长度较短的块而不是整件事可能是值得的,或者在测试整件事之前测试一些短前缀或横截面。
因此,这里有一些使用上述优化的潜在更快的优化(未经过测试/基准测试,部分原因是它取决于您的数据):
import typing as typ
def is_equal_str_bytes(
a: typ.Union[str, bytes],
b: typ.Union[str, bytes],
) -> bool:
if len(a) != len(b):
return False
if hash(a) != hash(b):
return False
if type(a) is type(b):
return a == b
if isinstance(a, bytes):
a, b = b, a
if a[:1000] != b[:1000].decode():
return False
if a[::1000] != b[::1000].decode():
return False
return a == b.decode()
我的基准代码:
import os
from timeit import repeat
n = 10**6
b = bytes(x & 127 for x in os.urandom(n))
s = b.decode()
assert hash(s) == hash(b)
setup = '''
from __main__ import s, b
s2 = b.decode() # Always fresh so it doesn't have a hash stored already
b2 = s.encode()
assert s2 is not s and b2 is not b
'''
exprs = [
's.encode()',
's.encode("utf-8")',
's.encode("ascii")',
'b.decode()',
'b.decode("utf-8")',
'b.decode("ascii")',
's == s2',
'b == b2',
's + "a"',
'b + b"a"',
'len(s)',
'len(b)',
's[:1000].encode()',
'b[:1000].decode()',
's[::1000].encode()',
'b[::1000].decode()',
'hash(s)',
'hash(b)',
'hash(s2)',
'hash(b2)',
'str(s)',
'str(b)',
'repr(s)',
'repr(b)',
]
for _ in range(3):
for e in exprs:
number = 100 if exprs.index(e) < exprs.index('hash(s)') else 1
t = min(repeat(e, setup, number=number)) / number
print('%8.2f us ' % (t * 1e6), e)
print()