github.com/number571/tendermint@v0.34.11-gost/crypto/merkle/tree.go (about) 1 package merkle 2 3 import ( 4 "hash" 5 "math/bits" 6 7 ghash "github.com/number571/go-cryptopro/gost_r_34_11_2012" 8 ) 9 10 // HashFromByteSlices computes a Merkle tree where the leaves are the byte slice, 11 // in the provided order. It follows RFC-6962. 12 func HashFromByteSlices(items [][]byte) []byte { 13 return hashFromByteSlices(ghash.New(ghash.H256), items) 14 } 15 16 func hashFromByteSlices(sha hash.Hash, items [][]byte) []byte { 17 switch len(items) { 18 case 0: 19 return emptyHash() 20 case 1: 21 return leafHashOpt(sha, items[0]) 22 default: 23 k := getSplitPoint(int64(len(items))) 24 left := hashFromByteSlices(sha, items[:k]) 25 right := hashFromByteSlices(sha, items[k:]) 26 return innerHashOpt(sha, left, right) 27 } 28 } 29 30 // HashFromByteSliceIterative is an iterative alternative to 31 // HashFromByteSlice motivated by potential performance improvements. 32 // (#2611) had suggested that an iterative version of 33 // HashFromByteSlice would be faster, presumably because 34 // we can envision some overhead accumulating from stack 35 // frames and function calls. Additionally, a recursive algorithm risks 36 // hitting the stack limit and causing a stack overflow should the tree 37 // be too large. 38 // 39 // Provided here is an iterative alternative, a test to assert 40 // correctness and a benchmark. On the performance side, there appears to 41 // be no overall difference: 42 // 43 // BenchmarkHashAlternatives/recursive-4 20000 77677 ns/op 44 // BenchmarkHashAlternatives/iterative-4 20000 76802 ns/op 45 // 46 // On the surface it might seem that the additional overhead is due to 47 // the different allocation patterns of the implementations. The recursive 48 // version uses a single [][]byte slices which it then re-slices at each level of the tree. 49 // The iterative version reproduces [][]byte once within the function and 50 // then rewrites sub-slices of that array at each level of the tree. 51 // 52 // Experimenting by modifying the code to simply calculate the 53 // hash and not store the result show little to no difference in performance. 54 // 55 // These preliminary results suggest: 56 // 57 // 1. The performance of the HashFromByteSlice is pretty good 58 // 2. Go has low overhead for recursive functions 59 // 3. The performance of the HashFromByteSlice routine is dominated 60 // by the actual hashing of data 61 // 62 // Although this work is in no way exhaustive, point #3 suggests that 63 // optimization of this routine would need to take an alternative 64 // approach to make significant improvements on the current performance. 65 // 66 // Finally, considering that the recursive implementation is easier to 67 // read, it might not be worthwhile to switch to a less intuitive 68 // implementation for so little benefit. 69 func HashFromByteSlicesIterative(input [][]byte) []byte { 70 items := make([][]byte, len(input)) 71 sha := ghash.New(ghash.H256) 72 73 for i, leaf := range input { 74 items[i] = leafHash(leaf) 75 } 76 77 size := len(items) 78 for { 79 switch size { 80 case 0: 81 return emptyHash() 82 case 1: 83 return items[0] 84 default: 85 rp := 0 // read position 86 wp := 0 // write position 87 for rp < size { 88 if rp+1 < size { 89 items[wp] = innerHashOpt(sha, items[rp], items[rp+1]) 90 rp += 2 91 } else { 92 items[wp] = items[rp] 93 rp++ 94 } 95 wp++ 96 } 97 size = wp 98 } 99 } 100 } 101 102 // getSplitPoint returns the largest power of 2 less than length 103 func getSplitPoint(length int64) int64 { 104 if length < 1 { 105 panic("Trying to split a tree with size < 1") 106 } 107 uLength := uint(length) 108 bitlen := bits.Len(uLength) 109 k := int64(1 << uint(bitlen-1)) 110 if k == length { 111 k >>= 1 112 } 113 return k 114 }