Update to go1.24.2

This commit is contained in:
Vorapol Rinsatitnon
2025-04-02 07:37:39 +07:00
parent 33f3fba1e8
commit f665e748c7
40 changed files with 795 additions and 119 deletions

View File

@@ -114,10 +114,22 @@ type B struct {
netBytes uint64
// Extra metrics collected by ReportMetric.
extra map[string]float64
// For Loop() to be executed in benchFunc.
// Loop() has its own control logic that skips the loop scaling.
// See issue #61515.
loopN int
// loop tracks the state of B.Loop
loop struct {
// n is the target number of iterations. It gets bumped up as we go.
// When the benchmark loop is done, we commit this to b.N so users can
// do reporting based on it, but we avoid exposing it until then.
n uint64
// i is the current Loop iteration. It's strictly monotonically
// increasing toward n.
//
// The high bit is used to poison the Loop fast path and fall back to
// the slow path.
i uint64
done bool // set when B.Loop return false
}
}
// StartTimer starts timing a test. This function is called automatically
@@ -130,6 +142,7 @@ func (b *B) StartTimer() {
b.startBytes = memStats.TotalAlloc
b.start = highPrecisionTimeNow()
b.timerOn = true
b.loop.i &^= loopPoisonTimer
}
}
@@ -142,6 +155,8 @@ func (b *B) StopTimer() {
b.netAllocs += memStats.Mallocs - b.startAllocs
b.netBytes += memStats.TotalAlloc - b.startBytes
b.timerOn = false
// If we hit B.Loop with the timer stopped, fail.
b.loop.i |= loopPoisonTimer
}
}
@@ -192,7 +207,9 @@ func (b *B) runN(n int) {
runtime.GC()
b.resetRaces()
b.N = n
b.loopN = 0
b.loop.n = 0
b.loop.i = 0
b.loop.done = false
b.ctx = ctx
b.cancelCtx = cancelCtx
@@ -203,6 +220,10 @@ func (b *B) runN(n int) {
b.StopTimer()
b.previousN = n
b.previousDuration = b.duration
if b.loop.n > 0 && !b.loop.done && !b.failed {
b.Error("benchmark function returned without B.Loop() == false (break or return in loop?)")
}
}
// run1 runs the first iteration of benchFunc. It reports whether more
@@ -312,8 +333,8 @@ func (b *B) launch() {
}()
// b.Loop does its own ramp-up logic so we just need to run it once.
// If b.loopN is non zero, it means b.Loop has already run.
if b.loopN == 0 {
// If b.loop.n is non zero, it means b.Loop has already run.
if b.loop.n == 0 {
// Run the benchmark for at least the specified amount of time.
if b.benchTime.n > 0 {
// We already ran a single iteration in run1.
@@ -368,38 +389,59 @@ func (b *B) ReportMetric(n float64, unit string) {
}
func (b *B) stopOrScaleBLoop() bool {
timeElapsed := highPrecisionTimeSince(b.start)
if timeElapsed >= b.benchTime.d {
t := b.Elapsed()
if t >= b.benchTime.d {
// Stop the timer so we don't count cleanup time
b.StopTimer()
// Commit iteration count
b.N = int(b.loop.n)
b.loop.done = true
return false
}
// Loop scaling
goalns := b.benchTime.d.Nanoseconds()
prevIters := int64(b.N)
b.N = predictN(goalns, prevIters, timeElapsed.Nanoseconds(), prevIters)
b.loopN++
prevIters := int64(b.loop.n)
b.loop.n = uint64(predictN(goalns, prevIters, t.Nanoseconds(), prevIters))
if b.loop.n&loopPoisonMask != 0 {
// The iteration count should never get this high, but if it did we'd be
// in big trouble.
panic("loop iteration target overflow")
}
b.loop.i++
return true
}
func (b *B) loopSlowPath() bool {
if b.loopN == 0 {
// Consistency checks
if !b.timerOn {
b.Fatal("B.Loop called with timer stopped")
}
if b.loop.i&loopPoisonMask != 0 {
panic(fmt.Sprintf("unknown loop stop condition: %#x", b.loop.i))
}
if b.loop.n == 0 {
// If it's the first call to b.Loop() in the benchmark function.
// Allows more precise measurement of benchmark loop cost counts.
// Also initialize b.N to 1 to kick start loop scaling.
b.N = 1
b.loopN = 1
// Also initialize target to 1 to kick start loop scaling.
b.loop.n = 1
// Within a b.Loop loop, we don't use b.N (to avoid confusion).
b.N = 0
b.loop.i++
b.ResetTimer()
return true
}
// Handles fixed iterations case
if b.benchTime.n > 0 {
if b.N < b.benchTime.n {
b.N = b.benchTime.n
b.loopN++
if b.loop.n < uint64(b.benchTime.n) {
b.loop.n = uint64(b.benchTime.n)
b.loop.i++
return true
}
b.StopTimer()
// Commit iteration count
b.N = int(b.loop.n)
b.loop.done = true
return false
}
// Handles fixed time case
@@ -440,13 +482,38 @@ func (b *B) loopSlowPath() bool {
// whereas b.N-based benchmarks must run the benchmark function (and any
// associated setup and cleanup) several times.
func (b *B) Loop() bool {
if b.loopN != 0 && b.loopN < b.N {
b.loopN++
// This is written such that the fast path is as fast as possible and can be
// inlined.
//
// There are three cases where we'll fall out of the fast path:
//
// - On the first call, both i and n are 0.
//
// - If the loop reaches the n'th iteration, then i == n and we need
// to figure out the new target iteration count or if we're done.
//
// - If the timer is stopped, it poisons the top bit of i so the slow
// path can do consistency checks and fail.
if b.loop.i < b.loop.n {
b.loop.i++
return true
}
return b.loopSlowPath()
}
// The loopPoison constants can be OR'd into B.loop.i to cause it to fall back
// to the slow path.
const (
loopPoisonTimer = uint64(1 << (63 - iota))
// If necessary, add more poison bits here.
// loopPoisonMask is the set of all loop poison bits. (iota-1) is the index
// of the bit we just set, from which we recreate that bit mask. We subtract
// 1 to set all of the bits below that bit, then complement the result to
// get the mask. Sorry, not sorry.
loopPoisonMask = ^uint64((1 << (63 - (iota - 1))) - 1)
)
// BenchmarkResult contains the results of a benchmark run.
type BenchmarkResult struct {
N int // The number of iterations.

View File

@@ -4,13 +4,22 @@
package testing
import (
"bytes"
"strings"
)
// See also TestBenchmarkBLoop* in other files.
func TestBenchmarkBLoop(t *T) {
var initialStart highPrecisionTime
var firstStart highPrecisionTime
var lastStart highPrecisionTime
var scaledStart highPrecisionTime
var runningEnd bool
runs := 0
iters := 0
firstBN := 0
restBN := 0
finalBN := 0
bRet := Benchmark(func(b *B) {
initialStart = b.start
@@ -18,8 +27,13 @@ func TestBenchmarkBLoop(t *T) {
for b.Loop() {
if iters == 0 {
firstStart = b.start
firstBN = b.N
} else {
restBN = max(restBN, b.N)
}
if iters == 1 {
scaledStart = b.start
}
lastStart = b.start
iters++
}
finalBN = b.N
@@ -37,6 +51,13 @@ func TestBenchmarkBLoop(t *T) {
if finalBN != iters || bRet.N != iters {
t.Errorf("benchmark iterations mismatch: %d loop iterations, final b.N=%d, bRet.N=%d", iters, finalBN, bRet.N)
}
// Verify that b.N was 0 inside the loop
if firstBN != 0 {
t.Errorf("want b.N == 0 on first iteration, got %d", firstBN)
}
if restBN != 0 {
t.Errorf("want b.N == 0 on subsequent iterations, got %d", restBN)
}
// Make sure the benchmark ran for an appropriate amount of time.
if bRet.T < benchTime.d {
t.Fatalf("benchmark ran for %s, want >= %s", bRet.T, benchTime.d)
@@ -45,8 +66,8 @@ func TestBenchmarkBLoop(t *T) {
if firstStart == initialStart {
t.Errorf("b.Loop did not reset the timer")
}
if lastStart != firstStart {
t.Errorf("timer was reset during iteration")
if scaledStart != firstStart {
t.Errorf("b.Loop stops and restarts the timer during iteration")
}
// Verify that it stopped the timer after the last loop.
if runningEnd {
@@ -54,4 +75,80 @@ func TestBenchmarkBLoop(t *T) {
}
}
// See also TestBenchmarkBLoop* in other files.
func TestBenchmarkBLoopBreak(t *T) {
var bState *B
var bLog bytes.Buffer
bRet := Benchmark(func(b *B) {
// The Benchmark function provides no access to the failure state and
// discards the log, so capture the B and save its log.
bState = b
b.common.w = &bLog
for i := 0; b.Loop(); i++ {
if i == 2 {
break
}
}
})
if !bState.failed {
t.Errorf("benchmark should have failed")
}
const wantLog = "benchmark function returned without B.Loop"
if log := bLog.String(); !strings.Contains(log, wantLog) {
t.Errorf("missing error %q in output:\n%s", wantLog, log)
}
// A benchmark that exits early should not report its target iteration count
// because it's not meaningful.
if bRet.N != 0 {
t.Errorf("want N == 0, got %d", bRet.N)
}
}
func TestBenchmarkBLoopError(t *T) {
// Test that a benchmark that exits early because of an error doesn't *also*
// complain that the benchmark exited early.
var bState *B
var bLog bytes.Buffer
bRet := Benchmark(func(b *B) {
bState = b
b.common.w = &bLog
for i := 0; b.Loop(); i++ {
b.Error("error")
return
}
})
if !bState.failed {
t.Errorf("benchmark should have failed")
}
const noWantLog = "benchmark function returned without B.Loop"
if log := bLog.String(); strings.Contains(log, noWantLog) {
t.Errorf("unexpected error %q in output:\n%s", noWantLog, log)
}
if bRet.N != 0 {
t.Errorf("want N == 0, got %d", bRet.N)
}
}
func TestBenchmarkBLoopStop(t *T) {
var bState *B
var bLog bytes.Buffer
bRet := Benchmark(func(b *B) {
bState = b
b.common.w = &bLog
for i := 0; b.Loop(); i++ {
b.StopTimer()
}
})
if !bState.failed {
t.Errorf("benchmark should have failed")
}
const wantLog = "B.Loop called with timer stopped"
if log := bLog.String(); !strings.Contains(log, wantLog) {
t.Errorf("missing error %q in output:\n%s", wantLog, log)
}
if bRet.N != 0 {
t.Errorf("want N == 0, got %d", bRet.N)
}
}