Repository navigation
perf: avoid buffering worker startup output - #2756
Open
andreybolonin wants to merge 4 commits into
Open
andreybolonin wants to merge 4 commits into
andreybolonin wants to merge 4 commits into
Conversation
dunglas
requested changes
Oct 23, 2026
dunglas
approved these changes
Oct 23, 2026
This branch has not been deployed
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Output written while a worker has no HTTP ResponseWriter currently allocates a
bytes.Buffer, copies the PHP bytes into it, and copies them again into the log message when INFO logging is enabled.Replace the intermediate buffer with one owned Go-string copy. Copy before
logger.Enabled, preserving the existing snapshot even if a custom logger changes the source bytes duringEnabled. Retain the copy when INFO is disabled to preserve ordering. HTTP writes still use the ResponseWriter directly, and completed requests keep returning the captured disconnect status.Added callback tests for empty/single-byte/long/binary output, enabled/disabled logs, HTTP errors and short writes, canceled contexts, completed requests, retained log records, and a logger mutating the source in
Enabled. Tests bridge the C argument types with reflection rather than introducing production test hooks or importing C from a_test.gofile.Validation
The same committed callback tests pass for both the previous and optimized implementations in the standalone harness. Fuzzing varies output bytes and dispatch modes, checking returned length/disconnect state, captured messages, response bytes and error attributes. No unsafe string referring to the C buffer is returned or retained.
-raceagainst both versions.gofmtandgit diff --checkpass../go.sh test -tags nowatcher -run '^TestUBWrite' -count=1 .; compilation stops at missingsapi/embed/php_embed.h. Local PHP 8.5.12 is NTS. Native callback/C integration, full FrankenPHP/Caddy suites and end-to-end load tests have not passed locally; they require a supported embed/ZTS environment.Time and allocation measurements
Baseline:
d138a78717413a589cb9af097483024c19f6ae92(main). PR head:9a39cf97fb0946682d9c8b28d94c452eb832c4e6. Apple M2, darwin/arm64, macOS 27.0, Go 1.27.3. This PR is independently based on main.The standalone harness extracts the complete
go_ub_writebody, including thread lookup, from each revision. C scalar types are replaced by Go named types with the same underlying 64-bit-target representation; a minimal thread/handler stub and the production context declaration provide the surrounding types. The committed callback tests are copied unchanged. This exercises Go logic, not the cgo bridge or PHP lifecycle.The benchmark calls that extracted callback directly; reflection is used only in correctness tests, never in timing loops. INFO logs go to a synchronous no-op slog.Handler; the HTTP ResponseWriter returns the length without copying. A request with a background context is present, so disconnect checks are included. Completed-request results around +/-0.1% are measurement noise.
Ten before/after pairs, alternating which version runs first,
-test.cpu=1 -test.benchmem, 120ms per sub-benchmark. Separate optimized binaries, without race instrumentation or PGO. Setup is outsideb.Loop, and results are consumed. Table entries are median and observed min-max in ns/op, not confidence intervals. Positive time reduction means faster. These are operation microbenchmarks, not whole HTTP-request throughput. These final measurements supersede the earlier narrower helper timings.bytes_0/log_disabledbytes_0/log_enabledbytes_0/http_responsebytes_0/donebytes_1/log_disabledbytes_1/log_enabledbytes_1/http_responsebytes_1/donebytes_64/log_disabledbytes_64/log_enabledbytes_64/http_responsebytes_64/donebytes_1024/log_disabledbytes_1024/log_enabledbytes_1024/http_responsebytes_1024/doneHeap allocation numbers count Go allocations during each operation. The optimization adds no cache or persistent state. Whole-process RSS, retained-process memory and PHP/C allocations were not measured, so zero/reduced B/op is not claimed to prove unchanged RSS.
Stack measurements
Generated optimized compiler assembly (
go test -c -gcflags=-S) for the same standalone function bodies:These are direct frames in the isolated Go compilation, not total nested stack peaks or native cgo-export wrapper frames. Linux and Windows were cross-compiled only; no runtime speed or test execution on those targets is claimed.
Reproduction
With supported PHP embed/ZTS development libraries installed, run the committed tests in the repository:
The following script reproduces the actual isolated timing setup without PHP. Save it outside the checkout and run it with Python 3 from this PR checkout. It extracts the baseline/current source, copies the committed tests, runs race checks, emits compiler assembly, and alternates ten benchmark pairs. For the output PR, the script also includes its Go-only benchmark fixture.
Standalone reproduction script
All baseline benchmark samples
All optimized benchmark samples
Fuzzing output