diff --git a/CHANGELOG.md b/CHANGELOG.md index 72ec5c47..75a23070 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -14,6 +14,9 @@ - Added support for `enum.ref` in `--interactive` prompts. Required vars using `enum.ref` now show the selection list like static enums, instead of falling back to free-form input (#2817 by @vmaerten). +- Further improved fingerprinting performance on large repositories: hashing + source files now reuses a single buffer, reducing memory allocations by ~98% + and wall-clock time by ~7% (#2925 by @vmaerten). ## v3.52.0 - 2026-07-02 diff --git a/internal/fingerprint/sources_checksum.go b/internal/fingerprint/sources_checksum.go index 3d341369..23a29b51 100644 --- a/internal/fingerprint/sources_checksum.go +++ b/internal/fingerprint/sources_checksum.go @@ -88,6 +88,10 @@ func (*ChecksumChecker) Kind() string { return "checksum" } +// readerOnly hides any WriterTo/ReaderFrom implementation of the wrapped +// reader, forcing io.CopyBuffer to use the caller-provided buffer. +type readerOnly struct{ io.Reader } + func (c *ChecksumChecker) checksum(t *ast.Task) (string, error) { sources, err := Globs(t.Dir, t.Sources, t.ShouldUseGitignore()) if err != nil { @@ -101,14 +105,18 @@ func (c *ChecksumChecker) checksum(t *ast.Task) (string, error) { if _, err := io.CopyBuffer(h, strings.NewReader(filepath.Base(f)), buf); err != nil { return "", err } - f, err := os.Open(f) + file, err := os.Open(f) if err != nil { return "", err } - if _, err = io.CopyBuffer(h, f, buf); err != nil { + // Wrap the file in a plain io.Reader so io.CopyBuffer cannot take the + // (*os.File).WriteTo fast path, which ignores buf and allocates a fresh + // 32KiB buffer for every file. Reusing buf keeps this loop allocation-free. + if _, err = io.CopyBuffer(h, readerOnly{file}, buf); err != nil { + file.Close() return "", err } - f.Close() + file.Close() } hash := h.Sum128()