types/key: use AvailableBuffer for WriteRawWithoutAllocating (#19102)

Use bufio.Writer.AvailableBuffer to write the 32-byte public key
directly into bufio's internal buffer as a single append+Write,
avoiding 32 separate WriteByte calls. Fall back to the existing
byte-at-a-time path when the buffer has insufficient space.

```
name                                old ns/op  new ns/op  speedup
NodeWriteRawWithoutAllocating-8     121        12.5       ~9.7x
(0 allocs/op in both)
```

Add BenchmarkNodeWriteRawWithoutAllocating and expand
TestNodeWriteRawWithoutAllocating to cover both fast (AvailableBuffer)
and slow (WriteByte fallback) paths with correctness and allocation
checks.

Updates tailscale/corp#38509

Signed-off-by: Mike O'Driscoll <mikeo@tailscale.com>
This commit is contained in:
Mike O'Driscoll
2026-03-24 18:08:08 -04:00
committed by GitHub
parent f52c1e3615
commit bb59942df2
2 changed files with 80 additions and 21 deletions
+16 -10
View File
@@ -253,18 +253,24 @@ func (k *NodePublic) ReadRawWithoutAllocating(br *bufio.Reader) error {
return err
}
// WriteRawWithoutAllocating writes out k as 32 bytes to bw.
// The writing is done ~3x slower than bw.Write, but in exchange is
// allocation-free.
// WriteRawWithoutAllocating writes out k as 32 big-endian bytes to bw.
//
// It uses AvailableBuffer to append directly into bufio's internal
// buffer without allocation, falling back to WriteByte when the
// buffer has insufficient space.
func (k NodePublic) WriteRawWithoutAllocating(bw *bufio.Writer) error {
// Equivalent to bw.Write(k.k[:]), but without causing an
// escape-related alloc.
//
// Dear future: if bw.Write(k.k[:]) stops causing stuff to escape,
// you should switch back to that.
// Fast path: enough space in the buffer to append directly.
if bw.Available() >= len(k.k) {
buf := bw.AvailableBuffer()
buf = append(buf, k.k[:]...)
_, err := bw.Write(buf)
return err
}
// Slow path: buffer nearly full. Write byte-at-a-time to let
// bufio flush as needed, avoiding a heap allocation from append
// growing past AvailableBuffer's capacity.
for _, b := range k.k {
err := bw.WriteByte(b)
if err != nil {
if err := bw.WriteByte(b); err != nil {
return err
}
}