runtime: add microbenchmarks for sizespecializedmalloc

This change adds code to generate benchmarks for calling mallocgc both
through the generated calls and directly, for use when benchmarking
sizespecializedmalloc changes.

For #79286

Change-Id: I0490fd8d72721cf25960086e82e55da96a6a6964
Reviewed-on: https://go-review.googlesource.com/c/go/+/775980
Reviewed-by: Michael Matloob <matloob@google.com>
Reviewed-by: Michael Pratt <mpratt@google.com>
LUCI-TryBot-Result: golang-scoped@luci-project-accounts.iam.gserviceaccount.com <golang-scoped@luci-project-accounts.iam.gserviceaccount.com>
This commit is contained in:
Michael Matloob 2026-05-05 09:53:46 -04:00
parent 9936a78b78
commit 15129eb73b
4 changed files with 1596 additions and 0 deletions

View file

@ -63,6 +63,12 @@ func main() {
if err := os.WriteFile(tablefile, mustFormat(generateTable(sizeToSizeClass)), 0666); err != nil {
log.Fatal(err)
}
benchmarkFile := "../malloc_bench_generated_test.go"
if err := os.WriteFile(benchmarkFile, mustFormat(append(inline(benchmarkConfig(classes, sizeToSizeClass)), []byte(generateTopBenchmark(classes, sizeToSizeClass))...)), 0666); err != nil {
log.Fatal(err)
}
}
// withLineNumbers returns b with line numbers added to help debugging.
@ -659,3 +665,70 @@ var mallocNoScanTable = [513]func(size uintptr, typ *_type, needzero bool) unsaf
return b.Bytes()
}
// benchmarkConfig produces an inlining config to stamp out microbenchmarks.
func benchmarkConfig(classes []class, sizeToSizeClass []uint8) generatorConfig {
config := generatorConfig{file: "../malloc_stubs_test.go"}
// Only generate specialized functions for sizes that don't have
// a header on 64-bit platforms. (They may have a header on 32-bit, but
// we will fall back to the non-specialized versions in that case)
scMax := sizeToSizeClass[smallScanNoHeaderMax]
str := fmt.Sprint
for sc := uint8(1); sc <= scMax; sc++ {
elemsize := classes[sc].size
config.specs = append(config.specs, spec{
templateFunc: "benchmarkStub",
name: fmt.Sprintf("benchmarkMallocgcNoscan%d", elemsize),
ops: []op{
{subBasicLit, "size_", str(elemsize)},
{foldCondition, "noscan_", str(true)},
},
})
config.specs = append(config.specs, spec{
templateFunc: "benchmarkStub",
name: fmt.Sprintf("benchmarkMallocgcScan%d", elemsize),
ops: []op{
{subBasicLit, "size_", str(elemsize)},
{foldCondition, "noscan_", str(false)},
},
})
}
for size := 1; size < tinySize; size++ {
config.specs = append(config.specs, spec{
templateFunc: "benchmarkStubTiny",
name: fmt.Sprintf("benchmarkMallocgcTiny%d", size),
ops: []op{{subBasicLit, "size_", str(size)}, {foldCondition, "noscan_", str(true)}},
})
}
return config
}
func generateTopBenchmark(classes []class, sizeToSizeClass []uint8) string {
scMax := sizeToSizeClass[smallScanNoHeaderMax]
bench := `func BenchmarkMallocgc(b *testing.B) {
b.Run("scan=noscan", func(b *testing.B) {
`
for size := 1; size < tinySize; size++ {
bench += fmt.Sprintf(`b.Run("size=%d", benchmarkMallocgcTiny%d)`, size, size) + "\n"
}
for sc := uint8(2); sc <= scMax; sc++ {
elemsize := classes[sc].size
bench += fmt.Sprintf(`b.Run("size=%d", benchmarkMallocgcNoscan%d)`, elemsize, elemsize) + "\n"
}
bench += `})
b.Run("scan=scan", func(b *testing.B) {
`
for sc := uint8(1); sc <= scMax; sc++ {
elemsize := classes[sc].size
bench += fmt.Sprintf(`b.Run("size=%d", benchmarkMallocgcScan%d)`, elemsize, elemsize) + "\n"
}
bench += `})
}`
return bench
}

View file

@ -2114,3 +2114,7 @@ func GetScanAlloc() uintptr {
c := getMCache(getg().m)
return c.scanAlloc
}
func MallocGC(size uintptr, typ *abi.Type, needzero bool) unsafe.Pointer {
return mallocgc(size, typ, needzero)
}

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,59 @@
// Copyright 2026 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package runtime_test
import (
"internal/abi"
"runtime"
"testing"
)
const size_ = 0
func benchmarkStubTiny(b *testing.B) {
const size = size_
type s struct {
v [size]byte
}
b.Run("kind=new", func(b *testing.B) {
for b.Loop() {
runtime.Escape(new(s))
}
})
typ := abi.TypeOf(s{})
b.Run("kind=mallocgc", func(b *testing.B) {
for b.Loop() {
runtime.Escape(runtime.MallocGC(size, typ, false))
}
})
}
const noscan_ = false
func benchmarkStub(b *testing.B) {
const size = size_
b.Run("kind=new", func(b *testing.B) {
for b.Loop() {
if noscan_ {
runtime.Escape(new(struct{ v [size / 8]uint64 }))
}
if !noscan_ {
runtime.Escape(new(struct{ v [size / 8]*uint64 }))
}
}
})
var typ *abi.Type
if noscan_ {
typ = abi.TypeOf(struct{ v [size / 8]uint64 }{})
}
if !noscan_ {
typ = abi.TypeOf(struct{ v [size / 8]*uint64 }{})
}
b.Run("kind=mallocgc", func(b *testing.B) {
for b.Loop() {
runtime.Escape(runtime.MallocGC(size, typ, true))
}
})
}