github.com/btcsuite/btcd@v0.24.0/blockchain/sizehelper.go (about)

     1  // Copyright (c) 2023 The btcsuite developers
     2  // Use of this source code is governed by an ISC
     3  // license that can be found in the LICENSE file.
     4  package blockchain
     5  
     6  import (
     7  	"math"
     8  )
     9  
    10  // These constants are related to bitcoin.
    11  const (
    12  	// outpointSize is the size of an outpoint.
    13  	//
    14  	// This value is calculated by running the following:
    15  	//	unsafe.Sizeof(wire.OutPoint{})
    16  	outpointSize = 36
    17  
    18  	// uint64Size is the size of an uint64 allocated in memory.
    19  	uint64Size = 8
    20  
    21  	// bucketSize is the size of the bucket in the cache map.  Exact
    22  	// calculation is (16 + keysize*8 + valuesize*8) where for the map of:
    23  	// map[wire.OutPoint]*UtxoEntry would have a keysize=36 and valuesize=8.
    24  	//
    25  	// https://github.com/golang/go/issues/34561#issuecomment-536115805
    26  	bucketSize = 16 + uint64Size*outpointSize + uint64Size*uint64Size
    27  
    28  	// This value is calculated by running the following on a 64-bit system:
    29  	//   unsafe.Sizeof(UtxoEntry{})
    30  	baseEntrySize = 40
    31  
    32  	// pubKeyHashLen is the length of a P2PKH script.
    33  	pubKeyHashLen = 25
    34  
    35  	// avgEntrySize is how much each entry we expect it to be.  Since most
    36  	// txs are p2pkh, we can assume the entry to be more or less the size
    37  	// of a p2pkh tx.  We add on 7 to make it 32 since 64 bit systems will
    38  	// align by 8 bytes.
    39  	avgEntrySize = baseEntrySize + (pubKeyHashLen + 7)
    40  )
    41  
    42  // The code here is shamelessely taken from the go runtime package.  All the relevant
    43  // code and variables are copied to here.  These values are only correct for a 64 bit
    44  // system.
    45  
    46  const (
    47  	_MaxSmallSize   = 32768
    48  	smallSizeDiv    = 8
    49  	smallSizeMax    = 1024
    50  	largeSizeDiv    = 128
    51  	_NumSizeClasses = 68
    52  	_PageShift      = 13
    53  	_PageSize       = 1 << _PageShift
    54  
    55  	MaxUintptr = ^uintptr(0)
    56  
    57  	// Maximum number of key/elem pairs a bucket can hold.
    58  	bucketCntBits = 3
    59  	bucketCnt     = 1 << bucketCntBits
    60  
    61  	// Maximum average load of a bucket that triggers growth is 6.5.
    62  	// Represent as loadFactorNum/loadFactorDen, to allow integer math.
    63  	loadFactorNum = 13
    64  	loadFactorDen = 2
    65  
    66  	// _64bit = 1 on 64-bit systems, 0 on 32-bit systems
    67  	_64bit = 1 << (^uintptr(0) >> 63) / 2
    68  
    69  	// PtrSize is the size of a pointer in bytes - unsafe.Sizeof(uintptr(0))
    70  	// but as an ideal constant. It is also the size of the machine's native
    71  	// word size (that is, 4 on 32-bit systems, 8 on 64-bit).
    72  	PtrSize = 4 << (^uintptr(0) >> 63)
    73  
    74  	// heapAddrBits is the number of bits in a heap address that's actually
    75  	// available for memory allocation.
    76  	//
    77  	// NOTE (guggero): For 64-bit systems, we just assume 40 bits of address
    78  	// space available, as that seems to be the lowest common denominator.
    79  	// See heapAddrBits in runtime/malloc.go of the standard library for
    80  	// more details
    81  	heapAddrBits = 32 + (_64bit * 8)
    82  
    83  	// maxAlloc is the maximum size of an allocation on the heap.
    84  	//
    85  	// NOTE(guggero): With the somewhat simplified heapAddrBits calculation
    86  	// above, this will currently limit the maximum allocation size of the
    87  	// UTXO cache to around 300GiB on 64-bit systems. This should be more
    88  	// than enough for the foreseeable future, but if we ever need to
    89  	// increase it, we should probably use the same calculation as the
    90  	// standard library.
    91  	maxAlloc = (1 << heapAddrBits) - (1-_64bit)*1
    92  )
    93  
    94  var class_to_size = [_NumSizeClasses]uint16{0, 8, 16, 24, 32, 48, 64, 80, 96, 112, 128, 144, 160, 176, 192, 208, 224, 240, 256, 288, 320, 352, 384, 416, 448, 480, 512, 576, 640, 704, 768, 896, 1024, 1152, 1280, 1408, 1536, 1792, 2048, 2304, 2688, 3072, 3200, 3456, 4096, 4864, 5376, 6144, 6528, 6784, 6912, 8192, 9472, 9728, 10240, 10880, 12288, 13568, 14336, 16384, 18432, 19072, 20480, 21760, 24576, 27264, 28672, 32768}
    95  var size_to_class8 = [smallSizeMax/smallSizeDiv + 1]uint8{0, 1, 2, 3, 4, 5, 5, 6, 6, 7, 7, 8, 8, 9, 9, 10, 10, 11, 11, 12, 12, 13, 13, 14, 14, 15, 15, 16, 16, 17, 17, 18, 18, 19, 19, 19, 19, 20, 20, 20, 20, 21, 21, 21, 21, 22, 22, 22, 22, 23, 23, 23, 23, 24, 24, 24, 24, 25, 25, 25, 25, 26, 26, 26, 26, 27, 27, 27, 27, 27, 27, 27, 27, 28, 28, 28, 28, 28, 28, 28, 28, 29, 29, 29, 29, 29, 29, 29, 29, 30, 30, 30, 30, 30, 30, 30, 30, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 31, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32, 32}
    96  var size_to_class128 = [(_MaxSmallSize-smallSizeMax)/largeSizeDiv + 1]uint8{32, 33, 34, 35, 36, 37, 37, 38, 38, 39, 39, 40, 40, 40, 41, 41, 41, 42, 43, 43, 44, 44, 44, 44, 44, 45, 45, 45, 45, 45, 45, 46, 46, 46, 46, 47, 47, 47, 47, 47, 47, 48, 48, 48, 49, 49, 50, 51, 51, 51, 51, 51, 51, 51, 51, 51, 51, 52, 52, 52, 52, 52, 52, 52, 52, 52, 52, 53, 53, 54, 54, 54, 54, 55, 55, 55, 55, 55, 56, 56, 56, 56, 56, 56, 56, 56, 56, 56, 56, 57, 57, 57, 57, 57, 57, 57, 57, 57, 57, 58, 58, 58, 58, 58, 58, 59, 59, 59, 59, 59, 59, 59, 59, 59, 59, 59, 59, 59, 59, 59, 59, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 60, 61, 61, 61, 61, 61, 62, 62, 62, 62, 62, 62, 62, 62, 62, 62, 62, 63, 63, 63, 63, 63, 63, 63, 63, 63, 63, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 64, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 65, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 66, 67, 67, 67, 67, 67, 67, 67, 67, 67, 67, 67, 67, 67, 67, 67, 67, 67, 67, 67, 67, 67, 67, 67, 67, 67, 67, 67, 67, 67, 67, 67, 67}
    97  
    98  // calculateRoughMapSize returns a close enough estimate of the
    99  // total memory allocated by a map.
   100  // hint should be the same value as the number you give when
   101  // making a map with the following syntax: make(map[k]v, hint)
   102  //
   103  // bucketsize is (16 + keysize*8 + valuesize*8).  For a map of:
   104  // map[int64]int64, keysize=8 and valuesize=8.  There are edge cases
   105  // where the bucket size is different that I can't find the source code
   106  // for. https://github.com/golang/go/issues/34561#issuecomment-536115805
   107  //
   108  // I suspect it's because of alignment and how the compiler handles it but
   109  // when compared with how much the compiler allocates, it's a couple hundred
   110  // bytes off.
   111  func calculateRoughMapSize(hint int, bucketSize uintptr) int {
   112  	// This code is copied from makemap() in runtime/map.go.
   113  	//
   114  	// TODO check once in a while to see if this algorithm gets
   115  	// changed.
   116  	mem, overflow := mulUintptr(uintptr(hint), uintptr(bucketSize))
   117  	if overflow || mem > maxAlloc {
   118  		hint = 0
   119  	}
   120  
   121  	// Find the size parameter B which will hold the requested # of elements.
   122  	// For hint < 0 overLoadFactor returns false since hint < bucketCnt.
   123  	B := uint8(0)
   124  	for overLoadFactor(hint, B) {
   125  		B++
   126  	}
   127  
   128  	// This code is copied from makeBucketArray() in runtime/map.go.
   129  	//
   130  	// TODO check once in a while to see if this algorithm gets
   131  	// changed.
   132  	//
   133  	// For small b, overflow buckets are unlikely.
   134  	// Avoid the overhead of the calculation.
   135  	base := bucketShift(B)
   136  	numBuckets := base
   137  	if B >= 4 {
   138  		// Add on the estimated number of overflow buckets
   139  		// required to insert the median number of elements
   140  		// used with this value of b.
   141  		numBuckets += bucketShift(B - 4)
   142  		sz := bucketSize * numBuckets
   143  		up := roundupsize(sz)
   144  		if up != sz {
   145  			numBuckets = up / bucketSize
   146  		}
   147  	}
   148  	total, _ := mulUintptr(bucketSize, numBuckets)
   149  
   150  	if base != numBuckets {
   151  		// Add 24 for mapextra struct overhead. Refer to
   152  		// runtime/map.go in the std library for the struct.
   153  		total += 24
   154  	}
   155  
   156  	// 48 is the number of bytes needed for the map header in a
   157  	// 64 bit system. Refer to hmap in runtime/map.go in the go
   158  	// standard library.
   159  	total += 48
   160  	return int(total)
   161  }
   162  
   163  // calculateMinEntries returns the minimum number of entries that will make the
   164  // map allocate the given total bytes.  -1 on the returned entry count will
   165  // make the map allocate half as much total bytes (for returned entry count that's
   166  // greater than 0).
   167  func calculateMinEntries(totalBytes int, bucketSize int) int {
   168  	// 48 is the number of bytes needed for the map header in a
   169  	// 64 bit system. Refer to hmap in runtime/map.go in the go
   170  	// standard library.
   171  	totalBytes -= 48
   172  
   173  	numBuckets := totalBytes / bucketSize
   174  	B := uint8(math.Log2(float64(numBuckets)))
   175  	if B < 4 {
   176  		switch B {
   177  		case 0:
   178  			return 0
   179  		case 1:
   180  			return 9
   181  		case 2:
   182  			return 14
   183  		default:
   184  			return 27
   185  		}
   186  	}
   187  
   188  	B -= 1
   189  
   190  	return (int(loadFactorNum * (bucketShift(B) / loadFactorDen))) + 1
   191  }
   192  
   193  // mulUintptr returns a * b and whether the multiplication overflowed.
   194  // On supported platforms this is an intrinsic lowered by the compiler.
   195  func mulUintptr(a, b uintptr) (uintptr, bool) {
   196  	if a|b < 1<<(4*PtrSize) || a == 0 {
   197  		return a * b, false
   198  	}
   199  	overflow := b > MaxUintptr/a
   200  	return a * b, overflow
   201  }
   202  
   203  // divRoundUp returns ceil(n / a).
   204  func divRoundUp(n, a uintptr) uintptr {
   205  	// a is generally a power of two. This will get inlined and
   206  	// the compiler will optimize the division.
   207  	return (n + a - 1) / a
   208  }
   209  
   210  // alignUp rounds n up to a multiple of a. a must be a power of 2.
   211  func alignUp(n, a uintptr) uintptr {
   212  	return (n + a - 1) &^ (a - 1)
   213  }
   214  
   215  // Returns size of the memory block that mallocgc will allocate if you ask for the size.
   216  func roundupsize(size uintptr) uintptr {
   217  	if size < _MaxSmallSize {
   218  		if size <= smallSizeMax-8 {
   219  			return uintptr(class_to_size[size_to_class8[divRoundUp(size, smallSizeDiv)]])
   220  		} else {
   221  			return uintptr(class_to_size[size_to_class128[divRoundUp(size-smallSizeMax, largeSizeDiv)]])
   222  		}
   223  	}
   224  	if size+_PageSize < size {
   225  		return size
   226  	}
   227  	return alignUp(size, _PageSize)
   228  }
   229  
   230  // overLoadFactor reports whether count items placed in 1<<B buckets is over loadFactor.
   231  func overLoadFactor(count int, B uint8) bool {
   232  	return count > bucketCnt && uintptr(count) > loadFactorNum*(bucketShift(B)/loadFactorDen)
   233  }
   234  
   235  // bucketShift returns 1<<b, optimized for code generation.
   236  func bucketShift(b uint8) uintptr {
   237  	// Masking the shift amount allows overflow checks to be elided.
   238  	return uintptr(1) << (b & (8*8 - 1))
   239  }