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  }