Python 如何提高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

我在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]
    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

  • 您可以在2)中看到,我使用了选项
    ('avx2',2,txs)
    ,这里
    2
    表示并行化参数,它不是多核而是单核并行化,这意味着将在一行中计算两个avx2寄存器。你应该把1,2,4,8,任何能让你更快计算的东西

  • <为了使图书馆被使用,你必须安装两件东西——一是编译器(Windows的MSVC和Linux的CLang(或GCC)),二是通过<代码> Python -M PIP安装Cython < /Cube >安装一次Cython模块,Cython是一个用于在Python中编程C++代码的AdvnCub库。在这里,它充当Python的
    .py
    和C++的
    .hpp
    模块之间的薄包装器。另外,我的代码是使用最现代的C++20标准编程的,请注意,您必须使用最新的C++20标准+