perf(resp): encode a three-argument RESP command with one Appendf

encodeCommands wrote one fmt.Appendf call per header and per argument. A
counter pipeline is almost entirely INCRBY and EXPIRE commands, which
carry three arguments each, so a metered request paid four Appendf calls
for every counter it wrote.

A command with exactly three arguments now goes through one Appendf with
the whole template. Every other arity keeps the per-argument loop. The
bytes on the wire do not change.

A new test compares arities two, three, four and five against the
previous encoder, so a wrong arity guard cannot emit a three-argument
header for a longer command.

Measured under yaegi as above: about 3% fewer bytes allocated per
request.

Claude-Session: https://claude.ai/code/session_01KwtV5ecajCrftcf8TkfPad
This commit is contained in:
2026-09-17 20:59:11 +01:00
parent 4fb5315bb3
commit 96b1a27ba5
2 changed files with 42 additions and 0 deletions
+28
View File
@@ -93,6 +93,34 @@ func TestEncodeCommands_MatchesReference(t *testing.T) {
}
}
// TestEncodeCommands_ArityBoundary pins the 3-argument fast path's exact
// boundary against referenceEncodeCommand: arities 2, 3, 4, and 5 must all
// match byte-for-byte. None of TestEncodeCommands_MatchesReference's own
// cases happen to use a 4- or 5-argument command, so a mutated guard (e.g.
// `len(args) == 3 || len(args) == 4`) would pass that whole test suite
// unnoticed — the fast path's body only ever reads args[0..2], so a
// 4-argument command routed into it would silently emit a "*3" header and
// drop its own 4th argument entirely, a malformed wire encoding no
// existing command arity exercises.
func TestEncodeCommands_ArityBoundary(t *testing.T) {
cases := []struct {
name string
args []string
}{
{name: "2 args, below the fast path", args: []string{"GET", "k"}},
{name: "3 args, the fast path itself", args: []string{"INCRBY", "k", "5"}},
{name: "4 args, just above the fast path", args: []string{"HSET", "k", "f", "v"}},
{name: "5 args, well above the fast path", args: []string{"MSET", "k1", "v1", "k2", "v2"}},
}
for _, c := range cases {
t.Run(c.name, func(t *testing.T) {
want := referenceEncodeCommand(c.args)
got := string(encodeCommands([][]string{c.args}))
assert.Equal(t, want, got)
})
}
}
// TestDecodeReply covers every RESP2 type respClient must decode: simple
// string, error, integer, bulk string (including the null bulk "$-1"), and
// a single top-level array (including the null array "*-1"). Every call