fix: merge two context keys into one in Hub.Batch

Reduce context.WithValue calls from 2 to 1 per Batch invocation.

| Metric     | Before | After | Delta |
|------------|--------|-------|-------|
| allocs/op  | 3      | 2     | -1    |
| B/op       | 128    | 80    | -48   |
| ns/op      | ~372   | ~352  | -5%   |
This commit is contained in:
Daisuke Maki 2026-02-21 22:15:03 +09:00
parent 6869d4dcfa
commit e2bc390c68
2 changed files with 37 additions and 14 deletions

29
hub/bench_test.go Normal file
View file

@ -0,0 +1,29 @@
package hub_test
import (
"context"
"testing"
"github.com/peco/peco/hub"
)
// BenchmarkHubBatch measures the allocation cost of Hub.Batch context setup.
func BenchmarkHubBatch(b *testing.B) {
h := hub.New(5)
ctx := context.Background()
// Drain query channel and call Done() so batch sends unblock
go func() {
for p := range h.QueryCh() {
p.Done()
}
}()
b.ResetTimer()
b.ReportAllocs()
for b.Loop() {
h.Batch(ctx, func(bctx context.Context) {
h.SendQuery(bctx, "test")
})
}
}

View file

@ -70,17 +70,16 @@ func New(bufsiz int) *Hub {
}
}
type batchPayloadKey struct{}
// batchLockKey is used to detect re-entrant Batch calls so that
// nested calls skip mutex acquisition and avoid deadlock.
type batchLockKey struct{}
// batchCtxKey is a single context key that signals both "this is a batch
// payload" and "the hub mutex is already held" for re-entrant detection.
// Using one key instead of two avoids a second context.WithValue allocation.
type batchCtxKey struct{}
// Batch allows you to synchronously send messages during the
// scope of f() being executed. The mutex is acquired automatically
// unless this is a nested Batch call (detected via context).
func (h *Hub) Batch(ctx context.Context, f func(ctx context.Context)) {
nested, _ := ctx.Value(batchLockKey{}).(bool)
nested, _ := ctx.Value(batchCtxKey{}).(bool)
if pdebug.Enabled {
g := pdebug.Marker("Batch (nested=%t)", nested)
@ -102,8 +101,7 @@ func (h *Hub) Batch(ctx context.Context, f func(ctx context.Context)) {
}
}()
batchCtx := context.WithValue(ctx, batchPayloadKey{}, true)
batchCtx = context.WithValue(batchCtx, batchLockKey{}, true)
batchCtx := context.WithValue(ctx, batchCtxKey{}, true)
f(batchCtx)
}
@ -132,12 +130,8 @@ func (p *Payload[T]) waitDone() {
// isBatchCtx reports whether the context was created by a Batch call.
func isBatchCtx(ctx context.Context) bool {
var isBatchMode bool
v := ctx.Value(batchPayloadKey{})
if vv, ok := v.(bool); ok {
isBatchMode = vv
}
return isBatchMode
v, _ := ctx.Value(batchCtxKey{}).(bool)
return v
}
// send is the low-level generic utility for sending typed payloads.