From a1e8c18a5a642af388791c02ba1acec7dd26fb3d Mon Sep 17 00:00:00 2001 From: jvoisin Date: Mon, 29 Jun 2026 16:42:21 +0200 Subject: [PATCH] encode: reduce allocations in AcceptedEncodings AcceptedEncodings runs for every request when response compression is enabled, so its allocations contribute directly to per-request garbage and GC pressure. While the gains are modest, this cuts allocations by two-thirds and bytes nearly in half on a hot path that executes on every compressed request. Fewer allocations means less work for the garbage collector, which at high request rates translates into lower GC frequency and steadier tail latency, not just faster execution of this one function in isolation. For each token in the Accept-Encoding header it called strings.Split(accepted, ";"), allocating a throwaway slice per encoding, and grew the prefs slice from empty. This commit replaces the per-token Split with strings.Cut (zero allocation) and presize prefs from the comma count. As requested per the policy, this commit adds a benchmark to exercise the change. With header "gzip, deflate, br;q=0.9, zstd;q=0.8": AcceptedEncodings - sec/op 1158.5n -> 771.8n (-33.38%) - B/op 408 -> 216 (-47.06%) - allocs/op 9 -> 3 (-66.67%) --- .../encode/accepted_encodings_bench_test.go | 17 +++++++++++++++++ modules/caddyhttp/encode/encode.go | 11 ++++++----- 2 files changed, 23 insertions(+), 5 deletions(-) create mode 100644 modules/caddyhttp/encode/accepted_encodings_bench_test.go diff --git a/modules/caddyhttp/encode/accepted_encodings_bench_test.go b/modules/caddyhttp/encode/accepted_encodings_bench_test.go new file mode 100644 index 00000000000..ff25c3136d2 --- /dev/null +++ b/modules/caddyhttp/encode/accepted_encodings_bench_test.go @@ -0,0 +1,17 @@ +package encode + +import ( + "net/http" + "net/http/httptest" + "testing" +) + +func BenchmarkAcceptedEncodings(b *testing.B) { + r := httptest.NewRequest(http.MethodGet, "/", nil) + r.Header.Set("Accept-Encoding", "gzip, deflate, br;q=0.9, zstd;q=0.8") + prefer := []string{"zstd", "br", "gzip"} + b.ReportAllocs() + for b.Loop() { + _ = AcceptedEncodings(r, prefer) + } +} diff --git a/modules/caddyhttp/encode/encode.go b/modules/caddyhttp/encode/encode.go index 8a4ac2b2363..887c9fc7346 100644 --- a/modules/caddyhttp/encode/encode.go +++ b/modules/caddyhttp/encode/encode.go @@ -504,16 +504,17 @@ func AcceptedEncodings(r *http.Request, preferredOrder []string) []string { return []string{} } - prefs := []encodingPreference{} + prefs := make([]encodingPreference, 0, strings.Count(acceptEncHeader, ",")+1) for accepted := range strings.SplitSeq(acceptEncHeader, ",") { - parts := strings.Split(accepted, ";") - encName := strings.ToLower(strings.TrimSpace(parts[0])) + encName, params, found := strings.Cut(accepted, ";") + encName = strings.ToLower(strings.TrimSpace(encName)) // determine q-factor qFactor := 1.0 - if len(parts) > 1 { - qFactorStr := strings.ToLower(strings.TrimSpace(parts[1])) + if found { + qFactorStr, _, _ := strings.Cut(params, ";") + qFactorStr = strings.ToLower(strings.TrimSpace(qFactorStr)) if strings.HasPrefix(qFactorStr, "q=") { if qFactorFloat, err := strconv.ParseFloat(qFactorStr[2:], 32); err == nil { if qFactorFloat >= 0 && qFactorFloat <= 1 {