logtail: avoid bytes.Buffer allocation

Re-use a pre-allocated bytes.Buffer struct and
shallow the copy the result of bytes.NewBuffer into it
to avoid allocating the struct.

Note that we're only reusing the bytes.Buffer struct itself
and not the underling []byte temporarily stored within it.

Updates #cleanup
Updates tailscale/corp#18514
Updates golang/go#67004

Signed-off-by: Joe Tsai <joetsai@digital-static.net>
This commit is contained in:
Joe Tsai 2024-04-23 15:48:38 -07:00
parent dd48cad89a
commit fe73052985
1 changed files with 10 additions and 2 deletions

View File

@ -213,6 +213,7 @@ type Logger struct {
procSequence uint64
flushTimer tstime.TimerController // used when flushDelay is >0
writeBuf [bufferSize]byte // owned by Write for reuse
bytesBuf bytes.Buffer // owned by appendTextOrJSONLocked for reuse
jsonDec jsontext.Decoder // owned by appendTextOrJSONLocked for reuse
shutdownStartMu sync.Mutex // guards the closing of shutdownStart
@ -724,9 +725,16 @@ func (l *Logger) appendTextOrJSONLocked(dst, src []byte, level int) []byte {
// whether it contains the reserved "logtail" name at the top-level.
var logtailKeyOffset, logtailValOffset, logtailValLength int
validJSON := func() bool {
// TODO(dsnet): Avoid allocation of bytes.Buffer struct.
// The jsontext.NewDecoder API operates on an io.Reader, for which
// bytes.Buffer provides a means to convert a []byte into an io.Reader.
// However, bytes.NewBuffer normally allocates unless
// we immediately shallow copy it into a pre-allocated Buffer struct.
// See https://go.dev/issue/67004.
l.bytesBuf = *bytes.NewBuffer(src)
defer func() { l.bytesBuf = bytes.Buffer{} }() // avoid pinning src
dec := &l.jsonDec
dec.Reset(bytes.NewBuffer(src))
dec.Reset(&l.bytesBuf)
if tok, err := dec.ReadToken(); tok.Kind() != '{' || err != nil {
return false
}