【发布时间】:2020-05-20 08:33:45
【问题描述】:
我正在用 Python 编写一个类似 rsync 的玩具工具。像许多类似的工具一样,它会首先使用一个非常快的哈希作为rolling hash,然后在找到匹配项后使用 SHA256(但后者不在此处:SHA256、MDA5 等太慢了)滚动哈希)。
我目前正在测试各种快速哈希方法:
import os, random, time
block_size = 1024 # 1 KB blocks
total_size = 10*1024*1024 # 10 MB random bytes
s = os.urandom(total_size)
t0 = time.time()
for i in range(len(s)-block_size):
h = hash(s[i:i+block_size])
print('rolling hashes computed in %.1f sec (%.1f MB/s)' % (time.time()-t0, total_size/1024/1024/(time.time()-t0)))
我得到:0.8 MB/s ... 所以 Python 内置的 hash(...) 函数在这里太慢了。
哪种解决方案可以在标准机器上实现至少 10 MB/s 的更快哈希?
-
我试过了
import zlib ... h = zlib.adler32(s[i:i+block_size])但也好不了多少(1.1 MB/s)
我试过
sum(s[i:i+block_size]) % modulo,它也很慢-
有趣的事实:即使没有任何哈希函数,循环本身也很慢!
t0 = time.time() for i in range(len(s)-block_size): s[i:i+block_size]我得到:只有 3.0 MB/s!因此,在
s上循环访问滚动块的简单事实已经很慢了。
与其重新发明轮子并编写我自己的哈希/或使用自定义 Rabin-Karp 算法,您有什么建议,首先加快这个循环,然后作为哈希?
编辑:上面“有趣的事实”慢循环的(部分)解决方案:
import os, random, time, zlib
from numba import jit
@jit()
def main(s):
for i in range(len(s)-block_size):
block = s[i:i+block_size]
total_size = 10*1024*1024 # 10 MB random bytes
block_size = 1024 # 1 KB blocks
s = os.urandom(total_size)
t0 = time.time()
main(s)
print('rolling hashes computed in %.1f sec (%.1f MB/s)' % (time.time()-t0, total_size/1024/1024/(time.time()-t0)))
使用 Numba,有很大的改进:40.0 MB/s,但这里仍然没有进行哈希处理。至少我们不会以 3 MB/s 的速度被阻止。
【问题讨论】:
-
每次重新计算整个块的哈希不是“滚动哈希”。您计算一个完整的哈希一次,然后对于每个步骤,您只使用两个字节的数据来更新该计算 - 一个在开始时刚刚退出块,一个在开始时刚刚进入块结尾。这与大多数哈希函数不兼容,但如果您使用所有字节的总和或 XOR,这将是微不足道的。
-
@jasonharper 即使有一个带有滑动窗口的循环并且没有散列,它已经很慢了(2.4MB/s)。我找到的唯一方法是 Numba (请参阅最后的更新问题)。
-
您的循环仍然在每一步制作一个块大小的数据切片 - 这是无缘无故地复制大量数据。
-
@jasonharper 我认为
block = s[i:i+block_size]没有复制,它只是对该块的引用/视图,对吗? -
我认为任何内置的 Python 类型在切片时都不会在现有对象中创建视图(这是一个有点问题的方法 - 由于保留原始切片的小切片太容易遇到内存问题活着的物体)。你必须使用
numpy来获得这种行为。
标签: python for-loop hash sha256 rolling-computation