Reduce heap allocs 2 (#46)

* reduce heap allocations in tcp logging; omit use of AppendFloat which allocates a metric sh*tton

* debugheaplog: better heap statistic logging

* heap: use string for pcap.Frame.Protocol

* add potential to eliminate Flags.String heap alloc, remove incorrect HEAP comments

* add StackAsync.DebugErr and httpraw.SetBytes

* many heap alloc reductions and replacement of bytes.Equal with internal.BytesEqual
This commit is contained in:
Pat Whittingslow
2026-02-28 19:20:46 +01:00
committed by GitHub
parent eab43c4653
commit 989bb6a0b9
22 changed files with 511 additions and 163 deletions
+5 -4
View File
@@ -1,7 +1,6 @@
package tcp
import (
"bytes"
"errors"
"log/slog"
"net"
@@ -20,6 +19,8 @@ var (
errNoRemoteAddr = errors.New("tcp: no remote address established")
errInvalidIP = errors.New("tcp: invalid IP")
errMismatchedIPVersion = errors.New("mismatched IP version")
errBadDemuxOffset = errors.New("bad offset in TCPConn.Recv")
errIPAddrMismatch = errors.New("IP addr mismatch on TCPConn")
)
// Conn builds on the [Handler] abstraction and adds IP header knowledge, time management, and familiar user facing API
@@ -329,14 +330,14 @@ func (conn *Conn) Demux(buf []byte, off int) (err error) {
conn.mu.Lock()
defer conn.mu.Unlock()
if off >= len(buf) {
return errors.New("bad offset in TCPConn.Recv")
return errBadDemuxOffset
}
raddr, _, id, _, err := internal.GetIPAddr(buf[:off])
if err != nil {
return err
}
if conn.isRaddrSet() && !bytes.Equal(conn.remoteAddr, raddr) {
return errors.New("IP addr mismatch on TCPConn")
if conn.isRaddrSet() && !internal.BytesEqual(conn.remoteAddr, raddr) {
return errIPAddrMismatch
}
conn.trace("tcpconn.Recv", slog.Uint64("lport", uint64(conn.h.LocalPort())), slog.Uint64("rport", uint64(conn.h.remotePort)))
err = conn.h.Recv(buf[off:])
+27 -16
View File
@@ -183,6 +183,8 @@ func (flags Flags) HasAny(mask Flags) bool { return flags&mask != 0 }
// Mask returns the flags with non-flag bits unset.
func (flags Flags) Mask() Flags { return flags & flagMask }
func (flags Flags) Invalid() bool { return flags&flagMask != flags }
// StringFlags returns human readable flag string. i.e:
//
// "[SYN,ACK]"
@@ -201,6 +203,8 @@ func (flags Flags) String() string {
return "[FIN,ACK]"
case pshack:
return "[PSH,ACK]"
case FlagFIN | FlagPSH | FlagACK:
return "[FIN,PSH,ACK]"
case FlagACK:
return "[ACK]"
case FlagSYN:
@@ -210,41 +214,48 @@ func (flags Flags) String() string {
case FlagRST:
return "[RST]"
}
if flags&flagMask != flags {
if flags.Invalid() {
return strInvalidTCPFlags
}
buf := make([]byte, 0, 2+3*bits.OnesCount16(uint16(flags)))
buf = append(buf, '[')
buf = flags.AppendFormat(buf)
buf = append(buf, ']')
return string(buf)
// Since Go 1.26 this should not allocate if returned string does not escape and is smaller than 32 bytes.
// https://go.dev/blog/allocation-optimizations
var buf [2 + 4*9]byte
buf[0] = '['
n := flags.format((*[36]byte)(buf[1:]))
buf[1+n] = ']'
return string(buf[:2+n])
}
// AppendFormat appends a human readable flag string to b returning the extended buffer.
func (flags Flags) AppendFormat(b []byte) []byte {
var buf [36]byte
n := flags.format(&buf)
return append(b, buf[:n]...)
}
const strInvalidTCPFlags = "<invalid TCP flags>"
// AppendFormat appends a human readable flag string to b returning the extended buffer.
func (flags Flags) AppendFormat(b []byte) []byte {
func (flags Flags) format(buf *[4 * 9]byte) (n int) {
if flags == 0 {
return b
} else if flags&flagMask != flags {
return append(b, strInvalidTCPFlags...)
return 0
} else if flags.Invalid() {
return copy(buf[:], strInvalidTCPFlags)
}
// String Flag const
const flaglen = 3
const strflags = "FINSYNRSTPSHACKURGECECWRNS "
var addcommas bool
for flags != 0 { // written by Github Copilot- looks OK.
i := bits.TrailingZeros16(uint16(flags))
if addcommas {
b = append(b, ',')
buf[n] = ','
n++
} else {
addcommas = true
}
b = append(b, strflags[i*flaglen:i*flaglen+flaglen]...)
n += copy(buf[n:], strflags[i*flaglen:i*flaglen+flaglen])
flags &= ^(1 << i)
}
return b
return n
}
// State enumerates states a TCP connection progresses through during its lifetime as per RFC9293.
+7 -1
View File
@@ -216,7 +216,13 @@ func (h *Handler) Recv(incomingPacket []byte) error {
}
}
if h.logenabled(internal.LevelTrace) {
h.trace("tcp.Handler:rx-done", slog.Uint64("port", uint64(h.localPort)), slog.Uint64("remoteport", uint64(remotePort)), slog.String("seg", segIncoming.String()))
h.trace("tcp.Handler:rx-done",
slog.Uint64("lport", uint64(h.localPort)),
slog.Uint64("rport", uint64(remotePort)),
slog.Uint64("seg.seq", uint64(segIncoming.SEQ)),
slog.Uint64("seg.ack", uint64(segIncoming.ACK)),
slog.Uint64("seg.datalen", uint64(segIncoming.DATALEN)),
)
}
return nil
}
+1 -2
View File
@@ -1,7 +1,6 @@
package tcp
import (
"bytes"
"errors"
"log/slog"
"net"
@@ -293,7 +292,7 @@ func getConn(conns []handler, remotePort uint16, remoteAddr []byte) int {
}
gotPort := conn.RemotePort()
gotaddr := conn.RemoteAddr()
if remotePort == gotPort && bytes.Equal(remoteAddr, gotaddr) {
if remotePort == gotPort && internal.BytesEqual(remoteAddr, gotaddr) {
return i
}
}
+96
View File
@@ -788,3 +788,99 @@ func FuzzTCBActions(f *testing.F) {
// hasPanicked = false
})
}
func TestFlagsString(t *testing.T) {
tests := []struct {
flags tcp.Flags
want string
}{
{flags: 0, want: "[]"},
{flags: tcp.FlagSYN, want: "[SYN]"},
{flags: tcp.FlagACK, want: "[ACK]"},
{flags: tcp.FlagFIN, want: "[FIN]"},
{flags: tcp.FlagRST, want: "[RST]"},
{flags: tcp.FlagSYN | tcp.FlagACK, want: "[SYN,ACK]"},
{flags: tcp.FlagFIN | tcp.FlagACK, want: "[FIN,ACK]"},
{flags: tcp.FlagPSH | tcp.FlagACK, want: "[PSH,ACK]"},
// Multi-flag combinations.
{flags: tcp.FlagFIN | tcp.FlagSYN | tcp.FlagACK, want: "[FIN,SYN,ACK]"},
{flags: tcp.FlagURG | tcp.FlagACK, want: "[ACK,URG]"},
{flags: tcp.FlagECE | tcp.FlagCWR, want: "[ECE,CWR]"},
{flags: tcp.FlagNS | tcp.FlagACK, want: "[ACK,NS ]"},
// All flags set.
{flags: tcp.FlagFIN | tcp.FlagSYN | tcp.FlagRST | tcp.FlagPSH | tcp.FlagACK | tcp.FlagURG | tcp.FlagECE | tcp.FlagCWR | tcp.FlagNS, want: "[FIN,SYN,RST,PSH,ACK,URG,ECE,CWR,NS ]"},
// Invalid flags (bits above mask).
{flags: 0xFFFF, want: "<invalid TCP flags>"},
}
for _, tc := range tests {
got := tc.flags.String()
if got != tc.want {
t.Errorf("Flags(%#x).String() = %q; want %q", uint16(tc.flags), got, tc.want)
}
}
}
func TestFlagsAppendFormat(t *testing.T) {
tests := []struct {
flags tcp.Flags
want string
}{
{flags: 0, want: ""},
{flags: tcp.FlagSYN, want: "SYN"},
{flags: tcp.FlagACK, want: "ACK"},
{flags: tcp.FlagFIN, want: "FIN"},
{flags: tcp.FlagRST, want: "RST"},
{flags: tcp.FlagSYN | tcp.FlagACK, want: "SYN,ACK"},
{flags: tcp.FlagFIN | tcp.FlagACK, want: "FIN,ACK"},
{flags: tcp.FlagPSH | tcp.FlagACK, want: "PSH,ACK"},
{flags: tcp.FlagFIN | tcp.FlagSYN | tcp.FlagACK, want: "FIN,SYN,ACK"},
{flags: tcp.FlagURG | tcp.FlagACK, want: "ACK,URG"},
{flags: tcp.FlagECE | tcp.FlagCWR, want: "ECE,CWR"},
{flags: tcp.FlagNS | tcp.FlagACK, want: "ACK,NS "},
{flags: tcp.FlagFIN | tcp.FlagSYN | tcp.FlagRST | tcp.FlagPSH | tcp.FlagACK | tcp.FlagURG | tcp.FlagECE | tcp.FlagCWR | tcp.FlagNS, want: "FIN,SYN,RST,PSH,ACK,URG,ECE,CWR,NS "},
{flags: 0xFFFF, want: "<invalid TCP flags>"},
}
for _, tc := range tests {
got := string(tc.flags.AppendFormat(nil))
if got != tc.want {
t.Errorf("Flags(%#x).AppendFormat(nil) = %q; want %q", uint16(tc.flags), got, tc.want)
}
}
// Test that AppendFormat appends to existing data.
prefix := []byte("flags=")
got := string(SYNACK.AppendFormat(prefix))
if want := "flags=SYN,ACK"; got != want {
t.Errorf("AppendFormat with prefix = %q; want %q", got, want)
}
}
func TestFlagsStringAppendFormatConsistency(t *testing.T) {
// Verify String() and AppendFormat() produce consistent results
// for all single-flag and common multi-flag values.
allFlags := []tcp.Flags{
0,
tcp.FlagFIN, tcp.FlagSYN, tcp.FlagRST, tcp.FlagPSH,
tcp.FlagACK, tcp.FlagURG, tcp.FlagECE, tcp.FlagCWR, tcp.FlagNS,
SYNACK, FINACK, PSHACK,
tcp.FlagFIN | tcp.FlagSYN | tcp.FlagRST | tcp.FlagPSH | tcp.FlagACK | tcp.FlagURG | tcp.FlagECE | tcp.FlagCWR | tcp.FlagNS,
}
for _, flags := range allFlags {
str := flags.String()
appended := string(flags.AppendFormat(nil))
// String() wraps in brackets, AppendFormat() does not.
if flags == 0 {
if str != "[]" {
t.Errorf("Flags(0).String() = %q; want %q", str, "[]")
}
if appended != "" {
t.Errorf("Flags(0).AppendFormat() = %q; want empty", appended)
}
continue
}
wantStr := "[" + appended + "]"
if str != wantStr {
t.Errorf("Flags(%#x): String()=%q inconsistent with AppendFormat()=%q (want %q)", uint16(flags), str, appended, wantStr)
}
}
}
+11 -6
View File
@@ -7,7 +7,12 @@ import (
)
var (
errPacketQueueFull = errors.New("packet queue full")
errPacketQueueFull = errors.New("packet queue full")
errQueuedPacketsLEZ = errors.New("queued packets <=0")
errInvalidBufSize = errors.New("invalid buffer size")
errSeqLessThanLast = errors.New("sequence number less than last sequence number")
errNoPacketToAck = errors.New("no packet to ack")
errAckUnsent = errors.New("ack of unsent packet")
)
const (
@@ -55,9 +60,9 @@ type ringidx struct {
func (rtx *ringTx) Reset(buf []byte, maxqueuedPackets int, iss Value) error {
buf = buf[:len(buf):len(buf)] // safely omit capacity section.
if maxqueuedPackets <= 0 {
return errors.New("queued packets <=0")
return errQueuedPacketsLEZ
} else if len(buf) < minBufferSize || len(buf) < maxqueuedPackets {
return errors.New("invalid buffer size")
return errInvalidBufSize
}
*rtx = ringTx{
@@ -126,7 +131,7 @@ func (rtx *ringTx) MakePacket(b []byte, currentSeq Value) (int, error) {
}
endSeq, ok := rtx.sentEndSeq()
if ok && currentSeq.LessThan(endSeq) {
return 0, errors.New("sequence number less than last sequence number")
return 0, errSeqLessThanLast
}
// Reading unsent ring consumes unsent and converts it to "sent".
unsent, _ := rtx.unsentRing()
@@ -316,11 +321,11 @@ func (sl *sentlist) AddPacket(datalen, off, bufsize int, seq Value) *ringidx {
func (sl *sentlist) RecvAck(ack Value, bufsize int) error {
newest := sl.Newest()
if newest == nil {
return errors.New("no packet to ack")
return errNoPacketToAck
}
endseq := newest.endSeq()
if endseq.LessThan(ack) {
return errors.New("ack of unsent packet")
return errAckUnsent
}
// Mark fully acked.
for i := 0; i < len(sl.pkts); i++ {