繁体   English   中英

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

[英]How to improve the speed of merkle root calculation?

我在 Python 中的实现为 ~1500 个输入哈希计算 merkle 根 hash:

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++ 中展开递归 function。 但是,性能并没有提高,大约花了 6 毫秒。

编辑

该文件可在此处获得: 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])

现在执行时间约为 4 毫秒:

$ time python3 test2.py 
b'289792577c66cd75f5b1f961e50bd8ce6f36adfc4c087dc1584f573df49bd32e'

real    0m3.697s
user    0m4.069s
sys     0m0.128s

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

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

我还创建了有关 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版本是我的SIMD(SSE2/AVX2/AVX512)实现。 如您所见,AVX2 版本比OpenSSL版本快3.5x倍,比原生 Python 的hashlib7.3x倍。

上述时序是在 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) 中可以看到我使用了 options ('avx2', 2, txs) ,这里的2表示并行化参数,不是多核而是单核并行化,意思是连续计算两个 avx2 寄存器。 您应该输入 1 或 2 或 4 或 8,无论您的计算速度如何。

In order for library to be used you have to have installed two things - one is compiler (MSVC for Windows and CLang (or GCC) for Linux), second - install one time Cython module through python -m pip install cython , Cython is an用于在 Python 中编程 C++ 代码的高级库,在这里它充当我的 Python 的.py和 C++ 的.hpp模块之间的薄包装器。 Also my code is programmed using most modern C++20 standard, be aware of this, you have to have most updated C++ compiler to be able to compile my code, for that download latest MSVC on Windows and/or latest CLang for Linux (通过命令bash -c "$(wget -O - https://apt.llvm.org/llvm.sh)"此处描述)。

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

在 my.py 模块中的所有函数版本中,您需要为事务提供字节列表,这意味着如果您有十六进制事务,那么您必须先取消它们的十六进制。 此外,所有函数都返回字节 hash,这意味着如果需要,您必须对其进行 hexlify。 这种仅字节传输仅出于性能原因。

我知道我的代码很难理解和使用。 因此,如果您对拥有最快代码的愿望非常认真,那么如果您愿意,请向我询问有关如何使用和理解我的代码的问题。 另外我应该说我的代码很脏,我并不是要为所有人制作一个干净闪亮的库,我只是想证明 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 调用。 我不知道有任何 package 这样做。

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

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM