mirror of
https://github.com/tailscale/tailscale.git
synced 2026-03-25 09:42:39 -04:00
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>
32 lines
956 B
Go
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
|
|
}
|