fileserver: reduce allocations in calculateEtag

calculateEtag runs for every static file served (and on conditional
If-None-Match revalidations). It used a non-presized strings.Builder plus
two strconv.FormatInt calls, each allocating an intermediate string and
triggering builder growth, which is about six allocations per call.

This commit builds the quoted etag into a fixed 32-byte stack array with
strconv.AppendInt instead, so only the final string is heap-allocated. The
output format is byte-for-byte identical. Two base-36 int64 values plus the
two quote bytes are at most 30 bytes (the worst case is MinInt64, which is
`1y2p0ij32e8e8`, so 13 digits plus a minus sign, so two times 14, plus the
quotes, for a total of 30 characters), so the buffer never spills. And even if
it did, which it doesn't append would simply grow it onto the heap without
changing the result.

Adds a benchmark, as required by the policy:

CalculateEtag:
- sec/op  347.6n -> 186.2n  (-46.42%)
- B/op 112 -> 56  (-50%)
- allocs/op 6 -> 2  (-66.67%)

It removes some CPU work, but more interestingly four of six per-file
allocations on a hot path of a very common handler, lowering per-request
garbage and GC pressure.
This commit is contained in:
jvoisin 2026-06-30 17:09:40 +02:00
parent 13a4c3f43c
commit 8c35fb2a38
2 changed files with 35 additions and 6 deletions

View file

@ -0,0 +1,27 @@
package fileserver
import (
"io/fs"
"testing"
"time"
)
type benchFileInfo struct {
size int64
mtime time.Time
}
func (fi benchFileInfo) Name() string { return "file.txt" }
func (fi benchFileInfo) Size() int64 { return fi.size }
func (fi benchFileInfo) Mode() fs.FileMode { return 0o644 }
func (fi benchFileInfo) ModTime() time.Time { return fi.mtime }
func (fi benchFileInfo) IsDir() bool { return false }
func (fi benchFileInfo) Sys() any { return nil }
func BenchmarkCalculateEtag(b *testing.B) {
fi := benchFileInfo{size: 1234567, mtime: time.Unix(1700000000, 123456789)}
b.ReportAllocs()
for b.Loop() {
_ = calculateEtag(fi)
}
}

View file

@ -765,12 +765,14 @@ func calculateEtag(d os.FileInfo) string {
if !usefulModTime(mtime) {
return ""
}
var sb strings.Builder
sb.WriteRune('"')
sb.WriteString(strconv.FormatInt(mtime.UnixNano(), 36))
sb.WriteString(strconv.FormatInt(d.Size(), 36))
sb.WriteRune('"')
return sb.String()
// A quoted etag of two base-36 int64 values is 30 bytes, thus fitting
// comfortably in 32 bytes, on the stack to prevent allocations
var buf [32]byte
b := append(buf[:0], '"')
b = strconv.AppendInt(b, mtime.UnixNano(), 36)
b = strconv.AppendInt(b, d.Size(), 36)
b = append(b, '"')
return string(b)
}
// Finds the first corresponding etag file for a given file in the file system and return its content