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 }