mirror of
https://github.com/lukaszraczylo/traefik-llmgateway.git
synced 2026-10-12 04:54:45 +00:00
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:
@@ -439,9 +439,23 @@ func closeConn(pc *respConn) {
|
||||
// under yaegi v0.16.1: cheaper than either presizing (the interpreted size
|
||||
// loop cost more than native slice regrowth) or appending raw bytes
|
||||
// directly (append with a string operand copies it under yaegi).
|
||||
//
|
||||
// A 3-argument command — INCRBY key delta or EXPIRE key seconds, this
|
||||
// client's two hottest commands by far (every counter increment sends
|
||||
// one, sometimes both) — takes a fast path instead: one fmt.Appendf call
|
||||
// building the whole "*3\r\n$len\r\narg\r\n..." frame at once, rather than
|
||||
// one header call plus 3 more per-argument calls. Measured under yaegi
|
||||
// v0.16.1 on the counter-increment pipeline this dominates: about 3%
|
||||
// fewer bytes per request. Any other argument count keeps the general
|
||||
// loop below — wire bytes are identical either way.
|
||||
func encodeCommands(cmds [][]string) []byte {
|
||||
var dst []byte
|
||||
for _, args := range cmds {
|
||||
if len(args) == 3 {
|
||||
dst = fmt.Appendf(dst, "*3\r\n$%d\r\n%s\r\n$%d\r\n%s\r\n$%d\r\n%s\r\n",
|
||||
len(args[0]), args[0], len(args[1]), args[1], len(args[2]), args[2])
|
||||
continue
|
||||
}
|
||||
dst = fmt.Appendf(dst, "*%d\r\n", len(args))
|
||||
for _, a := range args {
|
||||
dst = fmt.Appendf(dst, "$%d\r\n%s\r\n", len(a), a)
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user