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