perf(encode): emit in place, pool the buffer and cache interface flags
Test / test (push) Successful in 1m33s

Grouped emission walks the entries slice in three passes instead of copying them into per-kind slices, error paths render their key path only when an error names it, inline tables are measured once because a measuring encoder no longer nests another measurement, clean string runs are written in one write, and date-times render through the single-buffer path. The output buffer comes from a sync.Pool and returns to it within a 1 MiB retention cap, the output handed to the caller as a copy; repeated marshals keep the live heap flat, verified over 500 runs. The encoder resolves Marshaler and the text interfaces through the same per-type flag cache and hint the decoder uses, and docs/ARCHITECTURE.md now lists the caches and the pool as the library's shared state.

Representative document: 177 to 141 allocations, 12 230 to 6170 bytes per operation; long document: 97 175 to 63 660 allocations, 6.97 to 2.79 MB per operation, about 7.8 to about 3.4 ms.
This commit is contained in:
2026-09-20 22:15:38 +02:00
parent 9874464213
commit 2ae487e117
4 changed files with 337 additions and 118 deletions
+7 -1
View File
@@ -24,6 +24,7 @@ import (
"context"
"errors"
"fmt"
"slices"
)
// A SyntaxError describes a malformed TOML document, including the 1-based
@@ -423,7 +424,12 @@ func (e *Encoder) MarshalContext(ctx context.Context, v any) ([]byte, error) {
enc.ctx = ctx
enc.opts = *e
if err := enc.encode(v); err != nil {
enc.release()
return nil, err
}
return enc.bytes(), nil
// The output leaves the pooled buffer as a copy, so the next Marshal
// reuses the buffer without touching what the caller holds.
out := slices.Clone(enc.buf.Bytes())
enc.release()
return out, nil
}