Repository navigation
Conversation
- Skip retry log argument allocation when informational logging is disabled - Add optional initial and minimum Ring capacity while preserving defaults - Cover compatibility and capacity boundaries with regression tests - Record reproducible benchmarks and memory tradeoffs
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.
Summary
Reduce allocations when retry informational logging is disabled, and let bursty in-memory queues opt into retaining their Ring backing array with
WithQueueMinCapacity. The default remains two slots; queue limits, FIFO, retries and shutdown behavior are preserved. Existing Logger implementations remain compatible;InfoEnabled()is optional and checked on each retry.The PR deliberately excludes pre-existing local retry-timer reuse and Jitter fixes. The performance results below compare clean master with the actual PR implementation. Retention trades idle memory for fewer allocations; this PR does not claim a general end-to-end throughput improvement.
Clean-base measurements (six samples per version): retry-10 allocations 48 -> 38; retained Ring burst allocations 18 -> 0 per 1,024-task batch, with median time reduced about 28.5%. Full measurements and ranges are included below.
Related issues: N/A (no issue supplied).
Flow
flowchart TD Retry[queue.go: retry completes] --> Enabled[logger.go: optional InfoEnabled] Enabled -->|enabled or legacy logger| Log[queue.go: construct arguments and log] Enabled -->|disabled| Continue[queue.go: continue retry loop] Option[options.go: WithQueueMinCapacity] --> Init[ring.go: clamp and allocate slots] Init --> Drain[ring.go: dequeue and clear references] Drain --> Shrink[ring.go: shrink without crossing retained minimum] style Enabled fill:#dff0d8,stroke:#3c763d style Option fill:#dff0d8,stroke:#3c763d style Shrink fill:#dff0d8,stroke:#3c763dClassification and AI authorship
logger.go,options.go,queue.go,ring.go,burst_performance_test.go,performance_options_test.go.Verification
From the repository root on
perf/queue-allocation-costs, use Go 1.26.10 (seego.mod). Benchmark reproduction also needs Git, tar and Python 3. No services or credentials are required.go test -race ./...go tool -modfile=go.tools.mod golangci-lint rungo build -mod=readonly ./...in each_example/*modulegit diff --checkBehavioral scenarios (all Passed as part of the race suite):
go test -race -run '^TestRetryInfoEnabled$' .: legacy/enabled loggers emit three retry messages; disabled loggers emit none; dynamic enablement emits one; the task still makes four attempts.go test -race -run '^TestRingMinCapacity$' .: repeated fill/drain and wraparound preserve FIFO, clear task references, retain the requested minimum, support non-power-of-two values, cap retention against queue limits, and still reject full/shut-down queues. The NewPool path completes its submitted task.Run the following from the PR checkout to reproduce the candidate benchmarks:
To reproduce the baseline, extract
adf9a1cinto a separate temporary directory, copyburst_performance_test.gofrom this PR, and add this test-only adapter inretention_compat_test.go:This adapter lets the same benchmark compile against the baseline without changing its old capacity policy. Run the same command there. The other benchmark files already exist at the base commit. Run versions sequentially, alternating order; do not run race instrumentation or lint alongside timings. Remove the temporary baseline directory after inspection. Tests do not create external data.
Performance results: clean base versus PR
Measured on Apple M4 Pro, darwin/arm64, Go 1.26.10,
GOMAXPROCS=12(-cpu=12). Base:adf9a1c; measured implementation:f1fd2a4(subsequent removal of report files does not change production code or tests).Six samples per version: two rounds of three samples, 300ms per scenario. Order: base -> PR, then PR -> base. Runs were sequential, without concurrent Go tests/lint. All tasks are no-ops and use the empty logger; retries use a 1ns delay to expose framework overhead. End-to-end batches contain at most 1,024 tasks, include completion and final Release, and verify successful task counts. Producer goroutine creation is included in the multi-producer benchmarks.
Numbers below are medians. Negative time change means faster; throughput change is calculated as base time / PR time - 1. These are average processing costs, not per-task latency or p99. Ring results are per complete batch of 1,024 enqueue/dequeue pairs; other results are per completed task/handler.
Sample ranges and limits
WithQueueMinCapacity(1024)eliminates 18 steady-state allocations and 55,968 allocated bytes per 1,024-task burst. Median batch time falls about 28.5%. The default remains 2 slots and does not receive this opt-in benefit.Reports, raw-data files and reproduction scripts are kept locally and are not part of the PR's final file diff; benchmarks and regression tests remain in the PR.
Risk and rollback
No secrets, credentials, dependencies or network-facing interfaces are added. Custom InfoEnabled implementations must be concurrency-safe. Excessive retention settings allocate memory up front; default behavior is unchanged. Review minimum-capacity normalization, shrink/FIFO behavior and logger compatibility carefully. Revert the PR commits to roll back the options and logging guard.