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