Files
tailscale/util/bufiox/bufiox.go
Mike O'Driscoll 1403920367 derp,types,util: use bufio Peek+Discard for allocation-free fast reads (#19067)
Replace byte-at-a-time ReadByte loops with Peek+Discard in the DERP
read path. Peek returns a slice into bufio's internal buffer without
allocating, and Discard advances the read pointer without copying.

Introduce util/bufiox with a BufferedReader interface and ReadFull
helper that uses Peek+copy+Discard as an allocation-free alternative
to io.ReadFull.

  - derp.ReadFrameHeader: replace 5× ReadByte with Peek(5)+Discard(5),
    reading the frame type and length directly from the peeked slice.
    Remove now-unused readUint32 helper.

    name                  old ns/op  new ns/op  speedup
    ReadFrameHeader-8     24.2       12.4       ~2x
    (0 allocs/op in both)

  - key.NodePublic.ReadRawWithoutAllocating: replace 32× ReadByte with
    bufiox.ReadFull. Addresses the "Dear future" comment about switching
    away from byte-at-a-time reads once a non-escaping alternative exists.

    name                              old ns/op  new ns/op  speedup
    NodeReadRawWithoutAllocating-8    140        43.6       ~3.2x
    (0 allocs/op in both)

  - derpserver.handleFramePing: replace io.ReadFull with bufiox.ReadFull.

Updates tailscale/corp#38509

Signed-off-by: Mike O'Driscoll <mikeo@tailscale.com>
2026-03-24 10:52:20 -04:00

32 lines
956 B
Go

// Copyright (c) Tailscale Inc & contributors
// SPDX-License-Identifier: BSD-3-Clause
// Package bufiox provides extensions to the standard bufio package.
package bufiox
import "io"
// BufferedReader is an interface for readers that support peeking
// into an internal buffer, like [bufio.Reader].
type BufferedReader interface {
Peek(n int) ([]byte, error)
Discard(n int) (discarded int, err error)
}
// ReadFull reads exactly len(buf) bytes from r into buf, like
// [io.ReadFull], but without heap allocations. It uses Peek to
// access the buffered data directly, copies it into buf, then
// discards the consumed bytes. If an error occurs,
// discard is not called and the buffer is left unchanged.
func ReadFull(r BufferedReader, buf []byte) (int, error) {
b, err := r.Peek(len(buf))
if err != nil {
if len(b) > 0 && err == io.EOF {
err = io.ErrUnexpectedEOF
}
return 0, err
}
defer r.Discard(len(buf))
return copy(buf, b), nil
}