微信公众号搜"智元新知"关注
微信扫一扫可直接关注哦!

如何提高默克尔根计算的速度?

如何解决如何提高默克尔根计算的速度?

我在 Python 中的实现计算了大约 1500 个输入哈希的克尔根哈希:

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 root 计算了 1000 次,所以一次计算大约需要 5ms:

$ time python3 test.py 
b'289792577c66cd75f5b1f961e50bd8ce6f36adfc4c087dc1584f573df49bd32e'

real    0m5.132s
user    0m5.501s
sys     0m0.133s

如何提高计算速度?这段代码可以优化吗?

到目前为止,我已经尝试在 Python 和 C++ 中展开递归函数。然而,性能并没有提高,只花了~6ms。

编辑

文件可在此处获得: txids.txt

编辑 2

由于评论中的建议,我删除unhexlifyhexlify 不必要的步骤。在循环之前,列表准备好一次。

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

解决方法

我决定完全从头开始实施 SHA-256,并使用 SIMD 指令集(在此处阅读它们SSE2AVX2AVX512)。

因此,我的以下 AVX2 案例代码的速度比 OpenSSL 版本快 3.5x 倍,比 Python 的 7.3x 实现快 hashlib 倍。

我还创建了有关 C++ 版本的第二篇文章,see it here。阅读 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 版本是我的 SIMD (SSE2/AVX2/AVX512) 实现。如您所见,AVX2 版本比 3.5x 版本快 OpenSSL 倍,比原生 Python 的 7.3xhashlib 倍。

上面的计时是在 Google Colab 中完成的,因为它们有非常先进的 AVX2 CPU。

在底部提供库的代码,因为代码非常庞大,所以它作为单独的链接发布,因为它不符合 StackOverflow 的 30 KB 限制。有两个文件 sha256_simd.pysha256_simd.hpp。 Python 的文件包含计时和使用示例,还包含基于 Cython 的包装器以使用我在 .hpp 文件中提供的 C++ 库。这个python文件包含编译和运行代码所需的一切,只需将这两个文件放在附近并运行python文件即可。

我在 Windows(MSVC 编译器)和 Linux(CLang 编译器)上测试了这个程序/库。

我的库的使用示例位于 merkle_root_simd_example()main() 函数中。基本上你会做以下事情:

  1. 首先通过mod = sha256_simd_import(cap = 'avx2')导入我的库,每次程序运行只执行一次,不要多次执行,记住这个返回的模块到某个全局变量中。在 cap 参数中,您应该输入 CPU 支持的任何内容,它可以是 gensse2avx2avx512,以增加技术复杂性和提高速度. gen 是通用非 SIMD 操作,sse2 是 128 位操作,avx2 是 256 位操作,avx512 是 512 位操作。

  2. 导入后使用导入的模块,例如 mod.merkle_root_simd('avx2',2,txs)。在这里,您再次放置了 gen/sse2/avx2/avx512 技术之一。为什么又来了?第一次导入时放置编译选项,该选项告诉编译器支持给定和以下所有技术。这里放了将用于 merkle-root 调用的 SIMD 技术,该技术可以低于(但不能高于)编译技术。例如,如果您为 avx2 编译,那么您可以将库用于 gensse2avx2,但不能用于 avx512

  3. 你可以在 2) 中看到我使用了选项 ('avx2',txs),这里的 2 表示并行化参数,它不是多核而是单核并行化,这意味着两个 avx2 寄存器将是连续计算。您应该输入 1 或 2 或 4 或 8,无论哪个为您提供更快的计算。

为了使用库,您必须安装两件事 - 一是编译器(Windows 的 MSVC 和 Linux 的 CLang(或 GCC)),第二 - 通过 python -m pip install cython 安装一次 Cython 模块,Cython是一个用于在 Python 中编程 C++ 代码的高级库,在这里它充当我的 Python 的 .py 和 C++ 的 .hpp 模块之间的薄包装器。此外,我的代码是使用最现代的 C++20 标准编写的,请注意这一点,您必须拥有最新的 C++ 编译器才能编译我的代码,以便在 Windows 上下载最新的 MSVC 和/或最新的用于 Linux 的 CLang(通过命令 bash -c "$(wget -O - https://apt.llvm.org/llvm.sh)",它被描述为 here)。

在 .py 文件中,您可以看到我有时会提供额外的参数 has_ossl = True,win_ossl_dir = 'd:/bin/openssl/',仅当您需要将 OpenSSL 版本编译到我的库中时才需要这两个参数。 Windows openssl 可以从 here 下载。以后的 openssl 版本可以通过 mod.merkle_root_ossl(txs) 使用,只需为事务提供单个参数。

在我的 .py 模块中的所有函数版本中,您需要为交易提供字节列表,这意味着如果您有十六进制交易,那么您必须先对它们进行 unhexlify。此外,所有函数都返回字节哈希,这意味着如果需要,您必须对其进行十六进制化。这种仅限字节的往返传输仅出于性能原因。

我了解我的代码非常难以理解和使用。因此,如果您非常希望拥有最快的代码,那么如果您愿意,请向我询问有关如何使用和理解我的代码的问题。另外我应该说我的代码很脏,我并不是想为所有人制作一个干净闪亮的库,我只是想制作一个概念证明,SIMD 版本比 hashlib 的版本快得多,甚至比 openssl版本,因为只有当您的 CPU 非常先进以支持至少 SSE2/AVX2/AVX512 之一时,大多数 CPU 支持 SSE2,但并非所有 CPU 甚至支持 AVX2 和 AVX512。

sha256_simd.py

sha256_simd.hpp

,

在上次更新(2021 年 5 月 2 日 17:00)中,对 sha256(value).digest() 的调用在我的机器上占用了大约 80% 的时间。解决这个问题的可能解决方案很少。

第一个是使用 multiprocessing 并行化计算,假设每次迭代的工作都是独立的。下面是一个例子:

from multiprocessing.pool import Pool

# [...] same as in the question

def iteration(txids):
    merkle_root_hash = calculate_merkle_root(txids)
    merkle_root_hash = hexlify(merkle_root_hash[::-1])
    return merkle_root_hash

processPool = Pool()
res = processPool.map(iteration,[txids for i in range(1000)])

print(res[-1])

这在我的 6 核机器上快了 4 倍。

另一个解决方案是找到一个更快的 Python 模块,它可以同时计算多个 sha256 哈希值,以减少来自 CPython 解释器的昂贵的 C 调用。我不知道有任何包这样做。

最后,一种有效的解决方案是(至少部分地)用 C 或 C++ 重写昂贵的 calculate_merkle_root 计算并并行运行它。这应该比您当前的代码快得多,因为这消除了函数调用开销和多处理成本。有许多库可以计算 sha256 哈希值(例如 Crypto++ 库)。

版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。