(EDITED:提高综合素质)。
考虑如何str == str
is 用Python实现 https://stackoverflow.com/questions/43466106/what-happens-when-you-compare-two-strings-in-python,这首先得到一个id()
检查,长度检查,然后逐个元素进行。
这非常快并且可以理解,因为很多 Python 代码都依赖于此。
在一般情况下,不需要进一步优化,因为任意字符串很早就会有所不同。
然而,有两个用例有一些优化的空间:
- 你有一些部分信息how两个输入将会不同。
- 您在一组特定元素之间执行多重比较(请参阅@wim 评论)。
第一种情况的一个例子是:如果你知道当两个相同大小的输入不同时,它们是likely至少不同m
连续的元素,然后每隔一个进行比较k
元素与k < m
将是一个合理的赌注,例如:
def is_k_equal(a, b, k=4096):
if k in {0, 1}:
return a == b
else:
return a[::k] == b[::k]
def is_equal_partial(a, b, partial_match=is_k_equal):
return len(a) == len(b) and partial_match(a, b) and a == b
第二种情况的一个例子是:如果你想知道哪个p
输入出q
两两相等,计算哈希可能会有好处(例如使用hash()
,但其他选项可能同样有效)的输入,并且仅在哈希匹配时才执行完整比较。
不言而喻,如果您的哈希具有较高的冲突等级,它可能只会引入额外的开销(请参阅维基百科有关哈希的信息 https://en.wikipedia.org/wiki/List_of_hash_functions)。
输入的哈希值可以手动管理,或者您可以保护您的full与 a 中的哈希比较进行比较is_equal()
函数,例如:
def is_equal_hashing(a, b, hashing=hash):
return len(a) == len(b) and hashing(a) == hashing(b) and a == b
假设你的散列函数是memoized https://en.wikipedia.org/wiki/Memoization. For hash()
您不需要做任何其他事情,因为这些输入已经被记住了。
如果您要使用更高级的哈希(例如 crc32、md5 等),您可能需要自己添加记忆,例如@functools.lru_cache
。
该用例从这种方法中获益的条件(忽略比较哈希值所需的时间,该时间通常比要考虑的其他操作快得多)是:
t_h * n_h < t_c * n_c
with t_h
初始哈希计算时间,n_h
的数量unique要计算的哈希值,t_c
比较时间,以及n_c
在输入末尾附近失败的完整比较的数量。
当对您的输入将如何执行有疑问时,通常最好measure / profile你的代码。
在对记忆函数进行计时时必须小心(例如hash()
),因为,如果您对非记忆化路径的性能感兴趣,则不能像通常那样依赖同一输入的多次重复调用的计时,例如使用 IPython 的%timeit
使用默认参数。相反,您可以使用%timeit -n1 -r1
对于未缓存的计时。结果仅对数量级估计有用。
为了让您了解您的方法的可能成分有多快,以下是一些微基准:
import hashlib
import functools
def md5(data):
return hashlib.md5(data).digest()
@funtools.lru_cache(maxsize=16384)
def sha1(data):
return hashlib.sha1(data).digest()
def sha256(data):
return hashlib.sha1(data).digest()
def sha512(data):
return hashlib.sha1(data).digest()
import numpy as np
import numba as nb
@nb.jit(fastmath=True)
def hash_sum_nb(data, init=0):
dtype = np.uint64
nbytes = 8
n = len(data)
offset = n % nbytes
result = init
if offset:
body = np.frombuffer(data[:-offset], dtype=dtype)
tail = np.frombuffer(data[-offset:], dtype=np.uint8)
for x in tail:
result += x
else:
body = np.frombuffer(data, dtype=dtype)
for x in body:
result += x
return result + n
import zlib
import string
import random
n = 1_000_000
s = b''.join(string.printable[random.randrange(len(string.printable))].encode() for _ in range(n))
funcs = hash, hash, zlib.crc32, zlib.adler32, md5, sha1, sha1, sha256, sha512, hash_sum_nb
for func in funcs:
result = %timeit -n1 -r1 -q -o func(s)
print(f'{func.__name__:>12s} {result.best * 1e6:.3f} µs')
# hash 586.401 µs
# hash 0.853 µs
# crc32 976.128 µs
# adler32 468.452 µs
# md5 1790.659 µs
# sha1 1362.948 µs
# sha1 1.423 µs
# sha256 1347.432 µs
# sha512 1321.981 µs
# hash_sum_nb 64.768 µs
cases = {
'worst case': (s[:-1] + b'x', s[:-1] + b'y'),
'best case*': (s[:-1], s[:-2]),
'best case': (b'x' + s[:-1], b'y' + s[:-1]),
}
for name, (a, b) in cases.items():
result = %timeit -n1 -r1 -q -o a == b
print(f'str == str ({name:10s}) {result.best * 1e6:.3f} µs')
# str == str (worst case) 142.466 µs
# str == str (best case*) 0.856 µs
# str == str (best case ) 1.012 µs
a, b = (s[:-1] + b'x', s[:-1] + b'y')
result = %timeit -n1 -r1 -q -o is_k_equal(a, b)
print(f'{is_k_equal.__name__:>12s} {result.best * 1e6:.3f} µs')
# is_k_equal 10.037 µs
请注意,两者hash()
and sha1()
在同一输入上调用两次以显示记忆的效果。
有了这些数据(或您可以在输入/系统上生成的类似数字),就有可能制作出性能更高的字符串相等性比较。
请注意,我在整个答案中使用了bytes
反而。
的时间为str
对于大多数散列来说通常会更糟,因为处理编码需要额外的开销,但值得注意的例外hash()
.