Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 5 additions & 7 deletions element_create_benchmark_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -27,14 +27,14 @@ func (benchCreateUI) JawsUpdate(elem *Element) {}
//
// Batching is deliberate: b.StopTimer and b.StartTimer each call runtime.ReadMemStats, so
// toggling around a single sub-microsecond creation would leave the timed section tiny,
// calibration would pick an enormous b.N, and the excluded setup would run for minutes.
// calibration would pick an enormous iteration count, and the excluded setup would run
// for minutes.
// Amortising both calls over the batch keeps that honest, and deleting the batch keeps the
// Request registry bounded instead of growing across iterations. The reported figure is per
// batch of 64 Elements.
//
// The Jaws and Request are built before b.ResetTimer, and the final b.StopTimer excludes the
// deferred Close: both would otherwise be divided into every reported figure, time and
// allocations alike, making the result depend on b.N rather than on the Element.
// [testing.B.Loop] excludes construction and the deferred Close from the measurement,
// while the explicit timer calls exclude per-batch deletion.
func BenchmarkElementCreateBatch(b *testing.B) {
b.ReportAllocs()
const batch = 64
Expand All @@ -51,8 +51,7 @@ func BenchmarkElementCreateBatch(b *testing.B) {
var ui benchCreateUI
elems := make([]*Element, 0, batch)

b.ResetTimer()
for range b.N {
for b.Loop() {
elems = elems[:0]
for range batch {
elem := rq.NewElement(ui)
Expand All @@ -65,5 +64,4 @@ func BenchmarkElementCreateBatch(b *testing.B) {
rq.DeleteElements(elems)
b.StartTimer()
}
b.StopTimer()
}
3 changes: 1 addition & 2 deletions examples/minesweeper/main_benchmark_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -49,8 +49,7 @@ func BenchmarkSingleCellDirtyFanout(b *testing.B) {

cell := g.cells[0][0]
b.ReportAllocs()
b.ResetTimer()
for i := 0; i < b.N; i++ {
for b.Loop() {
// Mirror the framework's dirty dispatch: expand the toggle's tags and
// resolve each to its registered elements (Request.GetElements is the same
// tagMap lookup makeUpdateList performs). The sum is the number of element
Expand Down
41 changes: 16 additions & 25 deletions jaws_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -3137,8 +3137,7 @@ func BenchmarkRequestLifecyclePooling(b *testing.B) {
jw := newBenchPoolJaws(b)
cycleBenchRequest(jw, impl.newRq, impl.recycle, tags) // warm the pool and backing arrays
b.ReportAllocs()
b.ResetTimer()
for i := 0; i < b.N; i++ {
for b.Loop() {
cycleBenchRequest(jw, impl.newRq, impl.recycle, tags)
}
})
Expand Down Expand Up @@ -3204,8 +3203,7 @@ func BenchmarkRequestRecycleAfterHighWater(b *testing.B) {
}
jw.recycle(big)
b.ReportAllocs()
b.ResetTimer()
for i := 0; i < b.N; i++ {
for b.Loop() {
rq := jw.NewRequest(nil)
jw.recycle(rq)
runtime.KeepAlive(rq)
Expand All @@ -3223,8 +3221,7 @@ func BenchmarkRequestClaimStartFinish(b *testing.B) {
jw := newBenchPoolJaws(b)
r := httptest.NewRequest(http.MethodGet, "/", nil)
b.ReportAllocs()
b.ResetTimer()
for i := 0; i < b.N; i++ {
for b.Loop() {
rq := jw.NewRequest(r)
if jw.UseRequest(rq.JawsKey, r) != rq {
b.Fatal("claim failed")
Expand Down Expand Up @@ -3292,8 +3289,7 @@ func BenchmarkSubscriptionChannels(b *testing.B) {
rq := jw.NewRequest(nil)
defer jw.recycle(rq)
b.ReportAllocs()
b.ResetTimer()
for i := 0; i < b.N; i++ {
for b.Loop() {
msgCh := jw.subscribe(rq, 1)
if msgCh == nil {
b.Fatal("subscribe returned nil")
Expand Down Expand Up @@ -3353,8 +3349,7 @@ func BenchmarkDistributeDirt(b *testing.B) {
}

b.ReportAllocs()
b.ResetTimer()
for n := 0; n < b.N; n++ {
for b.Loop() {
b.StopTimer()
jw.setDirty(tags)
for _, rq := range reqs {
Expand Down Expand Up @@ -3387,14 +3382,14 @@ func BenchmarkRequestWantMessage(b *testing.B) {
b.Run("single-tag", func(b *testing.B) {
msg := wire.Message{Dest: hit, What: what.Update}
b.ReportAllocs()
for n := 0; n < b.N; n++ {
for b.Loop() {
_ = rq.wantMessage(&msg)
}
})
b.Run("multi-tag", func(b *testing.B) {
msg := wire.Message{Dest: []any{tag.Tag("nope"), hit}, What: what.Update}
b.ReportAllocs()
for n := 0; n < b.N; n++ {
for b.Loop() {
_ = rq.wantMessage(&msg)
}
})
Expand All @@ -3409,7 +3404,7 @@ func BenchmarkGetSendMsgs(b *testing.B) {
b.Run("idle/elems="+strconv.Itoa(n), func(b *testing.B) {
rq := newBenchRequest(b, n)
b.ReportAllocs()
for i := 0; i < b.N; i++ {
for b.Loop() {
_ = rq.getSendMsgs()
}
})
Expand All @@ -3427,13 +3422,13 @@ func BenchmarkGetElementByJid(b *testing.B) {
miss := Jid(n + 1)
b.Run("last/elems="+strconv.Itoa(n), func(b *testing.B) {
b.ReportAllocs()
for i := 0; i < b.N; i++ {
for b.Loop() {
_ = rq.getElementByJidLocked(last)
}
})
b.Run("miss/elems="+strconv.Itoa(n), func(b *testing.B) {
b.ReportAllocs()
for i := 0; i < b.N; i++ {
for b.Loop() {
_ = rq.getElementByJidLocked(miss)
}
})
Expand All @@ -3460,8 +3455,7 @@ func BenchmarkRequestHandleBroadcastCall(b *testing.B) {
}
msg := wire.Message{Dest: tc.dest, What: what.Call, Data: `app.refresh={"source":"server"}`}
b.ReportAllocs()
b.ResetTimer()
for i := 0; i < b.N; i++ {
for b.Loop() {
rq.handleBroadcast(msg, nil)
rq.muQueue.Lock()
rq.wsQueue = rq.wsQueue[:0]
Expand Down Expand Up @@ -3515,8 +3509,7 @@ func BenchmarkRequestEventDispatch(b *testing.B) {

var err error
b.ReportAllocs()
b.ResetTimer()
for i := 0; i < b.N; i++ {
for b.Loop() {
err = rq.callAllEventHandlers(elem.Jid(), what.Input, "value")
}
if err != nil {
Expand Down Expand Up @@ -3545,8 +3538,7 @@ func BenchmarkRequestEventDispatch(b *testing.B) {

var err error
b.ReportAllocs()
b.ResetTimer()
for i := 0; i < b.N; i++ {
for b.Loop() {
err = rq.callAllEventHandlers(0, what.Click, clickValue)
}
if err != nil {
Expand All @@ -3566,7 +3558,7 @@ func BenchmarkAppendJSQuote(b *testing.B) {
b.Run(c.name, func(b *testing.B) {
buf := make([]byte, 0, 256)
b.ReportAllocs()
for i := 0; i < b.N; i++ {
for b.Loop() {
buf = appendJSQuote(buf[:0], c.s)
}
_ = buf
Expand Down Expand Up @@ -3598,8 +3590,7 @@ func BenchmarkSendQueue(b *testing.B) {
}
ch := make(chan wire.WsMsg, k)
b.ReportAllocs()
b.ResetTimer()
for n := 0; n < b.N; n++ {
for b.Loop() {
rq.muQueue.Lock()
rq.wsQueue = append(rq.wsQueue[:0], msgs...)
rq.muQueue.Unlock()
Expand Down Expand Up @@ -3627,7 +3618,7 @@ func BenchmarkDistributeDirtSort(b *testing.B) {
}
b.Run("tags="+strconv.Itoa(n), func(b *testing.B) {
b.ReportAllocs()
for i := 0; i < b.N; i++ {
for b.Loop() {
benchDirtSink = sortedDirtTags(m)
}
})
Expand Down
2 changes: 2 additions & 0 deletions lib/assets/js_benchmark_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,8 @@ for (let i = 0; i < count; i++) {
}
`

// Keep b.N here so Node executes the calibrated message count in one process;
// starting a process per B.Loop iteration would measure process startup instead.
cmd := exec.CommandContext(b.Context(), node, "-e", script, jsPath, strconv.Itoa(b.N))
b.SetBytes(int64(len(frame)))
b.ResetTimer()
Expand Down
8 changes: 6 additions & 2 deletions lib/key/key_benchmark_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,12 @@ var appendBenchSink []byte
// BenchmarkAppend guards the base-32 encode hot path; it must stay
// allocation-light when appending into an existing buffer.
func BenchmarkAppend(b *testing.B) {
const benchmarkKey key.Key = 0x1234abcd
b.ReportAllocs()
for i := 0; i < b.N; i++ {
appendBenchSink = key.Append(appendBenchSink[:0], key.Key(0x1234abcd))
// Seed the backing array before B.Loop resets the timer so every measured
// append uses an existing buffer, including fixed-iteration runs.
appendBenchSink = key.Append(appendBenchSink[:0], benchmarkKey)
for b.Loop() {
appendBenchSink = key.Append(appendBenchSink[:0], benchmarkKey)
}
}
3 changes: 1 addition & 2 deletions lib/tag/tag_benchmark_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -49,8 +49,7 @@ var tagExpandBenchSink []any
func benchmarkTagExpandCase(b *testing.B, tag any) {
b.Helper()
b.ReportAllocs()
b.ResetTimer()
for i := 0; i < b.N; i++ {
for b.Loop() {
got, err := TagExpand(tag)
if err != nil {
b.Fatal(err)
Expand Down
2 changes: 2 additions & 0 deletions lib/ui/container_nested_reorder_benchmark_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,8 @@ func BenchmarkNestedContainersUpdate(b *testing.B) {

b.ReportAllocs()
b.ResetTimer()
// Keep b.N because validation crosses iterations with the timer stopped;
// B.Loop requires its condition to run while the timer is enabled.
for i := range b.N {
if benchmark.reorder {
tree.reverse()
Expand Down
11 changes: 6 additions & 5 deletions lib/ui/container_reuse_benchmark_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,7 @@ func benchReuseRequest(b *testing.B) (*jaws.Jaws, *jaws.Request) {
func BenchmarkContainerOfTemplatesUpdate(b *testing.B) {
b.ReportAllocs()
const rows = 200
// Keep b.N because fixture cleanup leaves the timer stopped between iterations.
for range b.N {
b.StopTimer()
jw, rq := benchReuseRequest(b)
Expand All @@ -91,6 +92,7 @@ func BenchmarkContainerOfTemplatesUpdate(b *testing.B) {
func BenchmarkContainerOfStableChildrenUpdate(b *testing.B) {
b.ReportAllocs()
const rows = 200
// Keep b.N because fixture cleanup leaves the timer stopped between iterations.
for range b.N {
b.StopTimer()
jw, rq := benchReuseRequest(b)
Expand Down Expand Up @@ -119,6 +121,7 @@ func BenchmarkContainerInitialRender(b *testing.B) {
container := NewContainer("div", bc)
b.ReportAllocs()
b.ResetTimer()
// Keep b.N to bound batches by the calibrated number of operations.
for completed := 0; completed < b.N; {
batchSize := min(benchmarkContainerBatchSize, b.N-completed)
elems := make([]*jaws.Element, batchSize)
Expand Down Expand Up @@ -149,7 +152,6 @@ func BenchmarkContainerInitialRender(b *testing.B) {
// BenchmarkContainerUnchangedUpdate measures a small reconciliation that reuses every
// child and emits no browser mutation.
func BenchmarkContainerUnchangedUpdate(b *testing.B) {
b.StopTimer()
tr := newReuseRequest(b)
bc := &benchStableContainer{contents: benchChildren(0, 4)}
container := NewContainer("div", bc)
Expand All @@ -159,12 +161,9 @@ func BenchmarkContainerUnchangedUpdate(b *testing.B) {
}
before := benchmarkContainerElements(b, elem, 4)
b.ReportAllocs()
b.ResetTimer()
b.StartTimer()
for range b.N {
for b.Loop() {
container.JawsUpdate(elem)
}
b.StopTimer()
after := benchmarkContainerElements(b, elem, 4)
for i := range before {
if after[i] != before[i] {
Expand Down Expand Up @@ -192,6 +191,7 @@ func BenchmarkContainerAppendRemoveUpdate(b *testing.B) {
b.ReportAllocs()
b.ResetTimer()
expanded := false
// Keep b.N to bound batches by the calibrated number of operations.
for completed := 0; completed < b.N; {
batchSize := min(benchmarkContainerBatchSize, b.N-completed)
startedExpanded := expanded
Expand Down Expand Up @@ -227,6 +227,7 @@ func BenchmarkContainerRegisterFirstUpdate(b *testing.B) {
container := NewContainer("div", bc)
b.ReportAllocs()
b.ResetTimer()
// Keep b.N to bound batches by the calibrated number of operations.
for completed := 0; completed < b.N; {
batchSize := min(benchmarkContainerBatchSize, b.N-completed)
elems := make([]*jaws.Element, batchSize)
Expand Down
5 changes: 3 additions & 2 deletions lib/ui/container_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -522,8 +522,7 @@ func benchChildren(start, count int) []jaws.UI {
func BenchmarkContainerValidateChildren(b *testing.B) {
b.ReportAllocs()
children := benchChildren(0, 1000)
b.ResetTimer() // exclude the one-time fixture allocation so -benchtime=1x is honest
for range b.N {
for b.Loop() {
if _, ok := firstUnusableChild(children); ok {
b.Fatal("unexpected unusable child")
}
Expand All @@ -533,6 +532,7 @@ func BenchmarkContainerValidateChildren(b *testing.B) {
func BenchmarkContainerUpdateAppendHeavy(b *testing.B) {
b.ReportAllocs()
const size = 1000
// Keep b.N because fixture cleanup leaves the timer stopped between iterations.
for range b.N {
b.StopTimer()
jw, rq := benchRequest(b)
Expand All @@ -553,6 +553,7 @@ func BenchmarkContainerUpdateAppendHeavy(b *testing.B) {
func BenchmarkContainerUpdateMixed(b *testing.B) {
b.ReportAllocs()
const size = 1000
// Keep b.N because fixture cleanup leaves the timer stopped between iterations.
for range b.N {
b.StopTimer()
jw, rq := benchRequest(b)
Expand Down
4 changes: 1 addition & 3 deletions lib/ui/http_request_benchmark_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -38,8 +38,7 @@ func BenchmarkHTTPPageRenderingByComplexity(b *testing.B) {

handler := bc.handler(b, jw)
b.ReportAllocs()
b.ResetTimer()
for i := 0; i < b.N; i++ {
for b.Loop() {
req := benchmarkPageRequest()
rr := httptest.NewRecorder()

Expand All @@ -52,7 +51,6 @@ func BenchmarkHTTPPageRenderingByComplexity(b *testing.B) {
benchmarkCleanupNoscript(b, jw, req.RemoteAddr, rr.Body.String())
b.StartTimer()
}
b.StopTimer()

if got := jw.RequestCount(); got != 0 {
b.Fatalf("request count after benchmark = %d, want 0", got)
Expand Down
Loading
Loading