github.com/project-88388/tendermint-v0.34.14-terra.2@v1.0.0/crypto/merkle/tree.go (about) 1 package merkle 2 3 import ( 4 "math/bits" 5 ) 6 7 // HashFromByteSlices computes a Merkle tree where the leaves are the byte slice, 8 // in the provided order. It follows RFC-6962. 9 func HashFromByteSlices(items [][]byte) []byte { 10 switch len(items) { 11 case 0: 12 return emptyHash() 13 case 1: 14 return leafHash(items[0]) 15 default: 16 k := getSplitPoint(int64(len(items))) 17 left := HashFromByteSlices(items[:k]) 18 right := HashFromByteSlices(items[k:]) 19 return innerHash(left, right) 20 } 21 } 22 23 // HashFromByteSliceIterative is an iterative alternative to 24 // HashFromByteSlice motivated by potential performance improvements. 25 // (#2611) had suggested that an iterative version of 26 // HashFromByteSlice 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 test to assert 33 // correctness and a benchmark. On the performance side, there appears to 34 // be no overall difference: 35 // 36 // BenchmarkHashAlternatives/recursive-4 20000 77677 ns/op 37 // BenchmarkHashAlternatives/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 HashFromByteSlice is pretty good 51 // 2. Go has low overhead for recursive functions 52 // 3. The performance of the HashFromByteSlice 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 HashFromByteSlicesIterative(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 emptyHash() 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 // getSplitPoint returns the largest power of 2 less than length 95 func getSplitPoint(length int64) int64 { 96 if length < 1 { 97 panic("Trying to split a tree with size < 1") 98 } 99 uLength := uint(length) 100 bitlen := bits.Len(uLength) 101 k := int64(1 << uint(bitlen-1)) 102 if k == length { 103 k >>= 1 104 } 105 return k 106 }