github.com/twelsh-aw/go/src@v0.0.0-20230516233729-a56fe86a7c81/runtime/mksizeclasses.go (about) 1 // Copyright 2016 The Go Authors. All rights reserved. 2 // Use of this source code is governed by a BSD-style 3 // license that can be found in the LICENSE file. 4 5 //go:build ignore 6 7 // Generate tables for small malloc size classes. 8 // 9 // See malloc.go for overview. 10 // 11 // The size classes are chosen so that rounding an allocation 12 // request up to the next size class wastes at most 12.5% (1.125x). 13 // 14 // Each size class has its own page count that gets allocated 15 // and chopped up when new objects of the size class are needed. 16 // That page count is chosen so that chopping up the run of 17 // pages into objects of the given size wastes at most 12.5% (1.125x) 18 // of the memory. It is not necessary that the cutoff here be 19 // the same as above. 20 // 21 // The two sources of waste multiply, so the worst possible case 22 // for the above constraints would be that allocations of some 23 // size might have a 26.6% (1.266x) overhead. 24 // In practice, only one of the wastes comes into play for a 25 // given size (sizes < 512 waste mainly on the round-up, 26 // sizes > 512 waste mainly on the page chopping). 27 // For really small sizes, alignment constraints force the 28 // overhead higher. 29 30 package main 31 32 import ( 33 "bytes" 34 "flag" 35 "fmt" 36 "go/format" 37 "io" 38 "log" 39 "math" 40 "math/bits" 41 "os" 42 ) 43 44 // Generate msize.go 45 46 var stdout = flag.Bool("stdout", false, "write to stdout instead of sizeclasses.go") 47 48 func main() { 49 flag.Parse() 50 51 var b bytes.Buffer 52 fmt.Fprintln(&b, "// Code generated by mksizeclasses.go; DO NOT EDIT.") 53 fmt.Fprintln(&b, "//go:generate go run mksizeclasses.go") 54 fmt.Fprintln(&b) 55 fmt.Fprintln(&b, "package runtime") 56 classes := makeClasses() 57 58 printComment(&b, classes) 59 60 printClasses(&b, classes) 61 62 out, err := format.Source(b.Bytes()) 63 if err != nil { 64 log.Fatal(err) 65 } 66 if *stdout { 67 _, err = os.Stdout.Write(out) 68 } else { 69 err = os.WriteFile("sizeclasses.go", out, 0666) 70 } 71 if err != nil { 72 log.Fatal(err) 73 } 74 } 75 76 const ( 77 // Constants that we use and will transfer to the runtime. 78 maxSmallSize = 32 << 10 79 smallSizeDiv = 8 80 smallSizeMax = 1024 81 largeSizeDiv = 128 82 pageShift = 13 83 84 // Derived constants. 85 pageSize = 1 << pageShift 86 ) 87 88 type class struct { 89 size int // max size 90 npages int // number of pages 91 } 92 93 func powerOfTwo(x int) bool { 94 return x != 0 && x&(x-1) == 0 95 } 96 97 func makeClasses() []class { 98 var classes []class 99 100 classes = append(classes, class{}) // class #0 is a dummy entry 101 102 align := 8 103 for size := align; size <= maxSmallSize; size += align { 104 if powerOfTwo(size) { // bump alignment once in a while 105 if size >= 2048 { 106 align = 256 107 } else if size >= 128 { 108 align = size / 8 109 } else if size >= 32 { 110 align = 16 // heap bitmaps assume 16 byte alignment for allocations >= 32 bytes. 111 } 112 } 113 if !powerOfTwo(align) { 114 panic("incorrect alignment") 115 } 116 117 // Make the allocnpages big enough that 118 // the leftover is less than 1/8 of the total, 119 // so wasted space is at most 12.5%. 120 allocsize := pageSize 121 for allocsize%size > allocsize/8 { 122 allocsize += pageSize 123 } 124 npages := allocsize / pageSize 125 126 // If the previous sizeclass chose the same 127 // allocation size and fit the same number of 128 // objects into the page, we might as well 129 // use just this size instead of having two 130 // different sizes. 131 if len(classes) > 1 && npages == classes[len(classes)-1].npages && allocsize/size == allocsize/classes[len(classes)-1].size { 132 classes[len(classes)-1].size = size 133 continue 134 } 135 classes = append(classes, class{size: size, npages: npages}) 136 } 137 138 // Increase object sizes if we can fit the same number of larger objects 139 // into the same number of pages. For example, we choose size 8448 above 140 // with 6 objects in 7 pages. But we can well use object size 9472, 141 // which is also 6 objects in 7 pages but +1024 bytes (+12.12%). 142 // We need to preserve at least largeSizeDiv alignment otherwise 143 // sizeToClass won't work. 144 for i := range classes { 145 if i == 0 { 146 continue 147 } 148 c := &classes[i] 149 psize := c.npages * pageSize 150 new_size := (psize / (psize / c.size)) &^ (largeSizeDiv - 1) 151 if new_size > c.size { 152 c.size = new_size 153 } 154 } 155 156 if len(classes) != 68 { 157 panic("number of size classes has changed") 158 } 159 160 for i := range classes { 161 computeDivMagic(&classes[i]) 162 } 163 164 return classes 165 } 166 167 // computeDivMagic checks that the division required to compute object 168 // index from span offset can be computed using 32-bit multiplication. 169 // n / c.size is implemented as (n * (^uint32(0)/uint32(c.size) + 1)) >> 32 170 // for all 0 <= n <= c.npages * pageSize 171 func computeDivMagic(c *class) { 172 // divisor 173 d := c.size 174 if d == 0 { 175 return 176 } 177 178 // maximum input value for which the formula needs to work. 179 max := c.npages * pageSize 180 181 // As reported in [1], if n and d are unsigned N-bit integers, we 182 // can compute n / d as ⌊n * c / 2^F⌋, where c is ⌈2^F / d⌉ and F is 183 // computed with: 184 // 185 // Algorithm 2: Algorithm to select the number of fractional bits 186 // and the scaled approximate reciprocal in the case of unsigned 187 // integers. 188 // 189 // if d is a power of two then 190 // Let F ← log₂(d) and c = 1. 191 // else 192 // Let F ← N + L where L is the smallest integer 193 // such that d ≤ (2^(N+L) mod d) + 2^L. 194 // end if 195 // 196 // [1] "Faster Remainder by Direct Computation: Applications to 197 // Compilers and Software Libraries" Daniel Lemire, Owen Kaser, 198 // Nathan Kurz arXiv:1902.01961 199 // 200 // To minimize the risk of introducing errors, we implement the 201 // algorithm exactly as stated, rather than trying to adapt it to 202 // fit typical Go idioms. 203 N := bits.Len(uint(max)) 204 var F int 205 if powerOfTwo(d) { 206 F = int(math.Log2(float64(d))) 207 if d != 1<<F { 208 panic("imprecise log2") 209 } 210 } else { 211 for L := 0; ; L++ { 212 if d <= ((1<<(N+L))%d)+(1<<L) { 213 F = N + L 214 break 215 } 216 } 217 } 218 219 // Also, noted in the paper, F is the smallest number of fractional 220 // bits required. We use 32 bits, because it works for all size 221 // classes and is fast on all CPU architectures that we support. 222 if F > 32 { 223 fmt.Printf("d=%d max=%d N=%d F=%d\n", c.size, max, N, F) 224 panic("size class requires more than 32 bits of precision") 225 } 226 227 // Brute force double-check with the exact computation that will be 228 // done by the runtime. 229 m := ^uint32(0)/uint32(c.size) + 1 230 for n := 0; n <= max; n++ { 231 if uint32((uint64(n)*uint64(m))>>32) != uint32(n/c.size) { 232 fmt.Printf("d=%d max=%d m=%d n=%d\n", d, max, m, n) 233 panic("bad 32-bit multiply magic") 234 } 235 } 236 } 237 238 func printComment(w io.Writer, classes []class) { 239 fmt.Fprintf(w, "// %-5s %-9s %-10s %-7s %-10s %-9s %-9s\n", "class", "bytes/obj", "bytes/span", "objects", "tail waste", "max waste", "min align") 240 prevSize := 0 241 var minAligns [pageShift + 1]int 242 for i, c := range classes { 243 if i == 0 { 244 continue 245 } 246 spanSize := c.npages * pageSize 247 objects := spanSize / c.size 248 tailWaste := spanSize - c.size*(spanSize/c.size) 249 maxWaste := float64((c.size-prevSize-1)*objects+tailWaste) / float64(spanSize) 250 alignBits := bits.TrailingZeros(uint(c.size)) 251 if alignBits > pageShift { 252 // object alignment is capped at page alignment 253 alignBits = pageShift 254 } 255 for i := range minAligns { 256 if i > alignBits { 257 minAligns[i] = 0 258 } else if minAligns[i] == 0 { 259 minAligns[i] = c.size 260 } 261 } 262 prevSize = c.size 263 fmt.Fprintf(w, "// %5d %9d %10d %7d %10d %8.2f%% %9d\n", i, c.size, spanSize, objects, tailWaste, 100*maxWaste, 1<<alignBits) 264 } 265 fmt.Fprintf(w, "\n") 266 267 fmt.Fprintf(w, "// %-9s %-4s %-12s\n", "alignment", "bits", "min obj size") 268 for bits, size := range minAligns { 269 if size == 0 { 270 break 271 } 272 if bits+1 < len(minAligns) && size == minAligns[bits+1] { 273 continue 274 } 275 fmt.Fprintf(w, "// %9d %4d %12d\n", 1<<bits, bits, size) 276 } 277 fmt.Fprintf(w, "\n") 278 } 279 280 func printClasses(w io.Writer, classes []class) { 281 fmt.Fprintln(w, "const (") 282 fmt.Fprintf(w, "_MaxSmallSize = %d\n", maxSmallSize) 283 fmt.Fprintf(w, "smallSizeDiv = %d\n", smallSizeDiv) 284 fmt.Fprintf(w, "smallSizeMax = %d\n", smallSizeMax) 285 fmt.Fprintf(w, "largeSizeDiv = %d\n", largeSizeDiv) 286 fmt.Fprintf(w, "_NumSizeClasses = %d\n", len(classes)) 287 fmt.Fprintf(w, "_PageShift = %d\n", pageShift) 288 fmt.Fprintln(w, ")") 289 290 fmt.Fprint(w, "var class_to_size = [_NumSizeClasses]uint16 {") 291 for _, c := range classes { 292 fmt.Fprintf(w, "%d,", c.size) 293 } 294 fmt.Fprintln(w, "}") 295 296 fmt.Fprint(w, "var class_to_allocnpages = [_NumSizeClasses]uint8 {") 297 for _, c := range classes { 298 fmt.Fprintf(w, "%d,", c.npages) 299 } 300 fmt.Fprintln(w, "}") 301 302 fmt.Fprint(w, "var class_to_divmagic = [_NumSizeClasses]uint32 {") 303 for _, c := range classes { 304 if c.size == 0 { 305 fmt.Fprintf(w, "0,") 306 continue 307 } 308 fmt.Fprintf(w, "^uint32(0)/%d+1,", c.size) 309 } 310 fmt.Fprintln(w, "}") 311 312 // map from size to size class, for small sizes. 313 sc := make([]int, smallSizeMax/smallSizeDiv+1) 314 for i := range sc { 315 size := i * smallSizeDiv 316 for j, c := range classes { 317 if c.size >= size { 318 sc[i] = j 319 break 320 } 321 } 322 } 323 fmt.Fprint(w, "var size_to_class8 = [smallSizeMax/smallSizeDiv+1]uint8 {") 324 for _, v := range sc { 325 fmt.Fprintf(w, "%d,", v) 326 } 327 fmt.Fprintln(w, "}") 328 329 // map from size to size class, for large sizes. 330 sc = make([]int, (maxSmallSize-smallSizeMax)/largeSizeDiv+1) 331 for i := range sc { 332 size := smallSizeMax + i*largeSizeDiv 333 for j, c := range classes { 334 if c.size >= size { 335 sc[i] = j 336 break 337 } 338 } 339 } 340 fmt.Fprint(w, "var size_to_class128 = [(_MaxSmallSize-smallSizeMax)/largeSizeDiv+1]uint8 {") 341 for _, v := range sc { 342 fmt.Fprintf(w, "%d,", v) 343 } 344 fmt.Fprintln(w, "}") 345 }