Files
tailscale/util/bufiox/bufiox_test.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

99 lines
2.0 KiB
Go

// Copyright (c) Tailscale Inc & contributors
// SPDX-License-Identifier: BSD-3-Clause
package bufiox
import (
"bufio"
"bytes"
"io"
"testing"
)
func TestReadFull(t *testing.T) {
data := []byte{0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08}
br := bufio.NewReader(bytes.NewReader(data))
var buf [5]byte
n, err := ReadFull(br, buf[:])
if err != nil {
t.Fatalf("ReadFull: %v", err)
}
if n != len(buf) {
t.Fatalf("n = %d, want %d", n, len(buf))
}
if want := [5]byte{0x01, 0x02, 0x03, 0x04, 0x05}; buf != want {
t.Fatalf("buf = %v, want %v", buf, want)
}
// Remaining bytes should still be readable.
var rest [3]byte
n, err = ReadFull(br, rest[:])
if err != nil {
t.Fatalf("ReadFull rest: %v", err)
}
if n != len(rest) {
t.Fatalf("rest n = %d, want %d", n, len(rest))
}
if want := [3]byte{0x06, 0x07, 0x08}; rest != want {
t.Fatalf("rest = %v, want %v", rest, want)
}
}
func TestReadFullShort(t *testing.T) {
data := []byte{0x01, 0x02}
br := bufio.NewReader(bytes.NewReader(data))
var buf [5]byte
_, err := ReadFull(br, buf[:])
if err != io.ErrUnexpectedEOF {
t.Fatalf("err = %v, want %v", err, io.ErrUnexpectedEOF)
}
}
func TestReadFullEmpty(t *testing.T) {
br := bufio.NewReader(bytes.NewReader(nil))
var buf [1]byte
_, err := ReadFull(br, buf[:])
if err != io.EOF {
t.Fatalf("err = %v, want %v", err, io.EOF)
}
}
func TestReadFullZeroAllocs(t *testing.T) {
data := make([]byte, 64)
rd := bytes.NewReader(data)
br := bufio.NewReader(rd)
var buf [32]byte
got := testing.AllocsPerRun(1000, func() {
rd.Reset(data)
br.Reset(rd)
_, err := ReadFull(br, buf[:])
if err != nil {
t.Fatalf("ReadFull: %v", err)
}
})
if got != 0 {
t.Fatalf("ReadFull allocs = %f, want 0", got)
}
}
type nopReader struct{}
func (nopReader) Read(p []byte) (int, error) { return len(p), nil }
func BenchmarkReadFull(b *testing.B) {
br := bufio.NewReader(nopReader{})
var buf [32]byte
b.ReportAllocs()
b.ResetTimer()
for range b.N {
_, err := ReadFull(br, buf[:])
if err != nil {
b.Fatal(err)
}
}
}