From af9405ac6e3d38f681743acc8b161c63f8b20efb Mon Sep 17 00:00:00 2001 From: Emmanuel T Odeke Date: Thu, 29 Feb 2024 19:41:21 +0300 Subject: [PATCH] merkle: optimize hashing by reusing sha256 in a pool MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This change optimizes SHA256 hashing for leafHash & innerHash by pooling and reusing hashes, which massively reduces on RAM consumption and even CPU time (benchmarking on my noisy machine is skewed but the results can be verified individually) producing the results below: ```shell $ benchstat before.txt after.txt name old time/op new time/op delta HashAlternatives/recursive-8 77.6µs ± 2% 77.0µs ± 2% ~ (p=0.165 n=10+10) HashAlternatives/iterative-8 76.3µs ± 1% 76.2µs ± 3% ~ (p=0.720 n=9+10) name old alloc/op new alloc/op delta HashAlternatives/recursive-8 25.4kB ± 0% 6.4kB ± 0% -74.94% (p=0.000 n=10+10) HashAlternatives/iterative-8 28.1kB ± 0% 9.1kB ± 0% -67.78% (p=0.000 n=10+10) name old allocs/op new allocs/op delta HashAlternatives/recursive-8 497 ± 0% 199 ± 0% -59.96% (p=0.000 n=10+10) HashAlternatives/iterative-8 498 ± 0% 200 ± 0% -59.84% (p=0.000 n=10+10) ``` Fixes #43 --- merkle/hash.go | 25 +++++++++++++++++++------ merkle/tree_test.go | 2 ++ 2 files changed, 21 insertions(+), 6 deletions(-) diff --git a/merkle/hash.go b/merkle/hash.go index 973629e..b5151af 100644 --- a/merkle/hash.go +++ b/merkle/hash.go @@ -2,6 +2,8 @@ package merkle import ( "crypto/sha256" + shash "hash" + "sync" ) // TODO: make these have a large predefined capacity @@ -17,15 +19,26 @@ func emptyHash() []byte { // returns sha256(0x00 || leaf) func leafHash(leaf []byte) []byte { - return hash(append(leafPrefix, leaf...)) + return hash(leafPrefix, leaf) } // returns sha256(0x01 || left || right) -func innerHash(left []byte, right []byte) []byte { - return hash(append(innerPrefix, append(left, right...)...)) +func innerHash(left, right []byte) []byte { + return hash(innerPrefix, left, right) } -func hash(bz []byte) []byte { - h := sha256.Sum256(bz) - return h[:] +var sha256Pool = &sync.Pool{New: func() any { return sha256.New() }} + +func hash(slices ...[]byte) []byte { + h := sha256Pool.Get().(shash.Hash) + defer func() { + h.Reset() + sha256Pool.Put(h) + }() + + for _, slice := range slices { + h.Write(slice) + } + + return h.Sum(nil) } diff --git a/merkle/tree_test.go b/merkle/tree_test.go index f529e35..1b28fab 100644 --- a/merkle/tree_test.go +++ b/merkle/tree_test.go @@ -121,12 +121,14 @@ func BenchmarkHashAlternatives(b *testing.B) { b.ResetTimer() b.Run("recursive", func(b *testing.B) { + b.ReportAllocs() for i := 0; i < b.N; i++ { _ = HashFromByteSlices(items) } }) b.Run("iterative", func(b *testing.B) { + b.ReportAllocs() for i := 0; i < b.N; i++ { _ = HashFromByteSlicesIterative(items) }