Go version
go version go1.24.6 linux/amd64
Output of go env in your module/workspace:
AR='ar'
CC='gcc'
CGO_CFLAGS='-O2 -g'
CGO_CPPFLAGS=''
CGO_CXXFLAGS='-O2 -g'
CGO_ENABLED='1'
CGO_FFLAGS='-O2 -g'
CGO_LDFLAGS='-O2 -g'
CXX='g++'
GCCGO='gccgo'
GO111MODULE=''
GOAMD64='v1'
GOARCH='amd64'
GOAUTH='netrc'
GOBIN=''
GOCACHE='/home/hxy/.cache/go-build'
GOCACHEPROG=''
GODEBUG=''
GOENV='/home/hxy/.config/go/env'
GOEXE=''
GOEXPERIMENT=''
GOFIPS140='off'
GOFLAGS=''
GOGCCFLAGS='-fPIC -m64 -pthread -Wl,--no-gc-sections -fmessage-length=0 -ffile-prefix-map=/tmp/go-build2828283384=/tmp/go-build -gno-record-gcc-switches'
GOHOSTARCH='amd64'
GOHOSTOS='linux'
GOINSECURE=''
GOMOD='/home/hxy/app/go1.24/src/go.mod'
GOMODCACHE='/home/hxy/go/pkg/mod'
GONOPROXY=''
GONOSUMDB=''
GOOS='linux'
GOPATH='/home/hxy/go'
GOPRIVATE=''
GOPROXY='https://goproxy.cn,direct'
GOROOT='/home/hxy/app/go1.24'
GOSUMDB='sum.golang.org'
GOTELEMETRY='local'
GOTELEMETRYDIR='/home/hxy/.config/go/telemetry'
GOTMPDIR=''
GOTOOLCHAIN='auto'
GOTOOLDIR='/home/hxy/app/go1.24/pkg/tool/linux_amd64'
GOVCS=''
GOVERSION='go1.24.6'
GOWORK=''
PKG_CONFIG='pkg-config'
What did you do?
I ran the standard-library TCP benchmarks directly, without an external benchmark
wrapper:
GOMAXPROCS=16 taskset -c 0-15 + go test -run='^$' + -bench='^BenchmarkTCP4(OneShot|Persistent)$' + -benchtime=1s -count=10 net
What did you see happen?
BenchmarkTCP4OneShot split into two repeatable groups, around 9.4 microseconds
and 22-24 microseconds per operation:
BenchmarkTCP4OneShot-16 125900 22375 ns/op
BenchmarkTCP4OneShot-16 123795 23303 ns/op
BenchmarkTCP4OneShot-16 126745 22781 ns/op
BenchmarkTCP4OneShot-16 114078 24431 ns/op
BenchmarkTCP4OneShot-16 127173 23575 ns/op
BenchmarkTCP4OneShot-16 127147 9425 ns/op
BenchmarkTCP4OneShot-16 123948 9423 ns/op
BenchmarkTCP4OneShot-16 125728 9475 ns/op
BenchmarkTCP4OneShot-16 126067 9520 ns/op
BenchmarkTCP4OneShot-16 126033 9423 ns/op
In a longer benchmark-suite run on the same host, BenchmarkTCP4Persistent also
occasionally split according to the calibrated iteration count:
BenchmarkTCP4Persistent-16 100 10227236 ns/op
BenchmarkTCP4Persistent-16 100 10226456 ns/op
BenchmarkTCP4Persistent-16 100 10226708 ns/op
BenchmarkTCP4Persistent-16 10000 102811 ns/op
BenchmarkTCP4Persistent-16 100 10170395 ns/op
BenchmarkTCP4Persistent-16 10000 102558 ns/op
The Persistent behavior did not reproduce in the standalone ten-count command,
where it stayed near 1.16 microseconds per operation, so it may require additional
system load or a transient stall.
What did you expect to see?
Repeated measurements of the same sub-benchmark should remain in one performance
population, apart from normal scheduler and socket noise. If OneShot is intended
to include connection lifecycle and goroutine creation effects, it would still be
useful for the benchmark to keep its concurrency and operation definition
independent of the adaptive value of b.N.
Why the benchmark implementation may contribute
In src/net/tcpsock_test.go:
- OneShot sets
conns := b.N and starts one goroutine per connection, with a
semaphore limiting active connections.
- Persistent derives both
conns and msgs from b.N.
- Listener setup, connection creation, goroutine startup, synchronization and
teardown are all inside the timed benchmark interval.
- Read, write, and dial failures are logged with
b.Log and terminate a worker,
but do not necessarily fail the benchmark or correct the reported operation
count.
This couples Go's adaptive benchmark calibration with goroutine creation, socket
lifecycle, and fixed setup/synchronization costs. A transient delay during
calibration can therefore change both b.N and the measured ns/op population.
Possible direction
Would it make sense to:
- Keep the worker/concurrency count fixed independently of
b.N.
- Exclude listener and worker setup from the timed interval.
- For Persistent, establish a fixed connection pool before starting the timer
and execute exactly b.N request/response operations.
- For OneShot, keep Dial/exchange/Close in the operation but reuse a fixed worker
pool instead of creating O(b.N) goroutines.
- Treat connection/read/write failures as a failed benchmark, or separately
report completed operations.
I can prepare a change and test it across amd64 and arm64 if this direction matches
the intended semantics of these benchmarks.
Go version
go version go1.24.6 linux/amd64
Output of
go envin your module/workspace:What did you do?
I ran the standard-library TCP benchmarks directly, without an external benchmark
wrapper:
What did you see happen?
BenchmarkTCP4OneShotsplit into two repeatable groups, around 9.4 microsecondsand 22-24 microseconds per operation:
In a longer benchmark-suite run on the same host,
BenchmarkTCP4Persistentalsooccasionally split according to the calibrated iteration count:
The Persistent behavior did not reproduce in the standalone ten-count command,
where it stayed near 1.16 microseconds per operation, so it may require additional
system load or a transient stall.
What did you expect to see?
Repeated measurements of the same sub-benchmark should remain in one performance
population, apart from normal scheduler and socket noise. If OneShot is intended
to include connection lifecycle and goroutine creation effects, it would still be
useful for the benchmark to keep its concurrency and operation definition
independent of the adaptive value of
b.N.Why the benchmark implementation may contribute
In
src/net/tcpsock_test.go:conns := b.Nand starts one goroutine per connection, with asemaphore limiting active connections.
connsandmsgsfromb.N.teardown are all inside the timed benchmark interval.
b.Logand terminate a worker,but do not necessarily fail the benchmark or correct the reported operation
count.
This couples Go's adaptive benchmark calibration with goroutine creation, socket
lifecycle, and fixed setup/synchronization costs. A transient delay during
calibration can therefore change both
b.Nand the measuredns/oppopulation.Possible direction
Would it make sense to:
b.N.and execute exactly
b.Nrequest/response operations.pool instead of creating
O(b.N)goroutines.report completed operations.
I can prepare a change and test it across amd64 and arm64 if this direction matches
the intended semantics of these benchmarks.