Python 如何提高merkle根计算的速度?
我在Python中的实现为~1500个输入哈希计算merkle根哈希:Python 如何提高merkle根计算的速度?,python,performance,recursion,sha256,merkle-tree,Python,Performance,Recursion,Sha256,Merkle Tree,我在Python中的实现为~1500个输入哈希计算merkle根哈希: import numpy as np from binascii import unhexlify, hexlify from hashlib import sha256 txids = np.loadtxt("txids.txt", dtype=str) def double_sha256(a, b): inp = unhexlify(a)[::-1] + unhexlify(b)[::-1
import numpy as np
from binascii import unhexlify, hexlify
from hashlib import sha256
txids = np.loadtxt("txids.txt", dtype=str)
def double_sha256(a, b):
inp = unhexlify(a)[::-1] + unhexlify(b)[::-1]
sha1 = sha256(inp).digest()
sha2 = sha256(sha1).digest()
return hexlify(sha2[::-1])
def calculate_merkle_root(inp_list):
if len(inp_list) == 1:
return inp_list[0]
out_list = []
for i in range(0, len(inp_list)-1, 2):
out_list.append(double_sha256(inp_list[i], inp_list[i+1]))
if len(inp_list) % 2 == 1:
out_list.append(double_sha256(inp_list[-1], inp_list[-1]))
return calculate_merkle_root(out_list)
for i in range(1000):
merkle_root_hash = calculate_merkle_root(txids)
print(merkle_root_hash)
由于merkle根计算了1000次,因此一次计算需要~5ms:
$ time python3 test.py
b'289792577c66cd75f5b1f961e50bd8ce6f36adfc4c087dc1584f573df49bd32e'
real 0m5.132s
user 0m5.501s
sys 0m0.133s
如何提高计算速度?这个代码可以优化吗
到目前为止,我已经尝试在Python和C++中展开递归函数。但是,性能没有提高,大约需要6毫秒
编辑 该文件可在以下位置获得: 编辑2 由于评论中的建议,我删除了不必要的unexlify
和hexlify
步骤。在循环之前,列表准备一次
def double_sha256(a, b):
inp = a + b
sha1 = sha256(inp).digest()
sha2 = sha256(sha1).digest()
return sha2
def map_func(t):
return unhexlify(t)[::-1]
txids = list(map(map_func, txids))
for i in range(1000):
merkle_root_hash = calculate_merkle_root(txids)
merkle_root_hash = hexlify(merkle_root_hash[::-1])
现在执行时间为~4ms:
$ time python3 test2.py
b'289792577c66cd75f5b1f961e50bd8ce6f36adfc4c087dc1584f573df49bd32e'
real 0m3.697s
user 0m4.069s
sys 0m0.128s
在上一次更新(2021年5月2日17:00)中,调用sha256(value).digest()
在我的机器上花费了大约80%的时间。解决这个问题几乎没有什么可行的办法
第一种方法是使用多处理
并行计算,假设每次迭代的工作是独立的。以下是一个例子:
来自multiprocessing.pool导入池的
#[……]与问题中相同
def迭代(txids):
merkle_root_hash=计算merkle_root(txids)
merkle_root_hash=hexlify(merkle_root_hash[::-1])
返回merkle\u根\u散列
processPool=Pool()
res=processPool.map(范围(1000)内i的迭代[txids])
打印(分辨率[-1])
这在我的6核机器上快了4倍
另一个解决方案是找到一个更快的Python模块,它可以同时计算多个sha256哈希,以减少来自CPython解释器的昂贵的C调用。我不知道有哪个包裹会这样做
最后,一个有效的解决方案是(至少部分地)重写昂贵的<代码> CalpTeaMulkLyRooSoo/<代码>计算,并在C或C++中并行运行。这应该比您当前的代码快得多,因为这消除了函数调用开销和多处理成本。有许多库可以计算sha256散列(就像库一样)。我决定从头开始完全实现SHA-256,并使用指令集(请在此处阅读) 因此,我下面针对AVX2案例的代码的速度比OpenSSL版本快3.5倍,比Python的hashlib实现快7.3倍
我还创建了有关C++版本的相关第二篇文章。阅读C++帖子,了解更多关于我库的细节,这个Python帖子更高。 首先提供时间安排:
simple 3.006
openssl 1.426
simd gen 1 1.639
simd gen 2 1.903
simd gen 4 0.847
simd gen 8 0.457
simd sse2 1 0.729
simd sse2 2 0.703
simd sse2 4 0.718
simd sse2 8 0.776
simd avx2 1 0.461
simd avx2 2 0.41
simd avx2 4 0.549
simd avx2 8 0.521
这里的simple
是hashlib的版本,与您提供的版本相近,openssl
代表openssl版本,其余的simd
版本是mine simd(SSE2/AVX2/AVX512)实现。如您所见,AVX2版本比OpenSSL
版本快3.5x
倍,比本机Python的hashlib
快7.3x
倍
上面的计时是在谷歌完成的,因为他们有相当先进的AVX2 CPU可用
在底部提供库的代码,因为代码非常庞大,所以它作为单独的链接发布,因为它不符合堆栈溢出的30KB
限制。有两个文件sha256_simd.py
和sha256_simd.hpp
。Python的文件包含定时和使用示例,还有基于包装的文件,使用我的.Hp文件中的C++库。这个python文件包含编译和运行代码所需的一切,只需将这两个文件放在附近并运行python文件
我在Windows(MSVC编译器)和Linux(CLang编译器)上测试了这个程序/库
我的库的使用示例位于merkle\u root\u simd\u example()
和main()
函数中。基本上你会做以下事情:
mod=sha256\u simd\u import(cap='avx2')
导入我的库,每次程序运行只执行一次,不要执行多次,记住将此模块返回到某个全局变量中。在cap
参数中,您应该放置CPU支持的任何参数,它可以是gen
或sse2
或avx2
或avx512
,以提高技术复杂性和速度gen
是通用的非SIMD操作,sse2
是128位操作,avx2
是256位操作,avx512
是512位操作
mod.merkle\u root\u simd('avx2',2,txs)
。在这里,您再次放置了一种gen
/sse2
/avx2
/avx512
技术。又为什么?第一次导入时,输入编译选项,该选项告诉编译器支持给定技术和以下所有技术。在这里,您将使用用于merkle根调用的SIMD技术,该技术可以比编译技术低(但不能高)。例如,如果为avx2
编译,则可以将库用于gen
或sse2
或avx2
,但不能用于avx512
('avx2',2,txs)
,这里2
表示并行化参数,它不是多核而是单核并行化,这意味着将在一行中计算两个avx2寄存器。你应该把1,2,4,8,任何能让你更快计算的东西
.py
和C++的.hpp
模块之间的薄包装器。另外,我的代码是使用最现代的C++20标准编程的,请注意,您必须使用最新的C++20标准+