簡體   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