[英]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
由於評論中的建議,我刪除了unhexlify
和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])
現在執行時間約為 4 毫秒:
$ time python3 test2.py
b'289792577c66cd75f5b1f961e50bd8ce6f36adfc4c087dc1584f573df49bd32e'
real 0m3.697s
user 0m4.069s
sys 0m0.128s
我決定從頭開始完全實現 SHA-256 並使用SIMD指令集(在此處閱讀它們SSE2 、 AVX2 、 AVX512 )。
因此,我下面的 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 的hashlib
快7.3x
倍。
上述時序是在 Google Colab中完成的,因為它們具有相當先進的 AVX2 CPU。
在底部提供庫的代碼,因為代碼非常龐大,它作為單獨的鏈接發布,因為它不符合 StackOverflow 的30 KB
限制。 有兩個文件sha256_simd.py
和sha256_simd.hpp
。 Python 的文件包含計時和使用示例以及基於Cython的包裝器,以使用我在.hpp 文件中提供的 C++ 庫。 這個 python 文件包含編譯和運行代碼所需的一切,只需將這兩個文件放在附近並運行 python 文件。
我在 Windows(MSVC 編譯器)和 Linux(CLang 編譯器)上測試了這個程序/庫。
我的庫的使用示例位於merkle_root_simd_example()
和main()
函數中。 基本上你做以下事情:
首先通過mod = sha256_simd_import(cap = 'avx2')
導入我的庫,每個程序運行只執行一次,不要多次執行,記住這個返回的模塊到一些全局變量中。 在cap
參數中,您應該放置您的 CPU 支持的任何內容,它可以是gen
或sse2
或avx2
或avx512
以增加技術復雜性和提高速度。 gen
是通用的非 SIMD 操作, sse2
是 128 位操作, avx2
是 256 位操作, avx512
是 512 位操作。
導入后使用導入的模塊,例如mod.merkle_root_simd('avx2', 2, txs)
。 在這里,您再次放置了gen
/ sse2
/ avx2
/ avx512
技術之一。 為什么又來了? 第一次導入時放置編譯選項,告訴編譯器支持給定的和所有以下技術。 這里你放了將用於 merkle-root 調用的 SIMD 技術,該技術可以低於(但不能高於)編譯技術。 例如,如果您為avx2
編譯,那么您可以將庫用於gen
或sse2
或avx2
,但不能用於avx512
。
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。
在上次更新(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.