Skip to content

Commit 4cc46b0

Browse files
committed
go/vt/sqlparser: improve performance in TrackedBuffer formatting
Two minor things: * decently improve formatting integers (%d) * WriteArg calls buf.Grow to avoid potentially 2 allocations For integer formatting, buf.WriteString(fmt.Sprintf(...)) was about the worst way to do it. fmt.Sprintf itself allocates a new string, plus it's %d formatting is much more robust in handling padding and whatnot. First alternative for free win was using `fmt.Fprintf(&buf, ...)` instead, which simply avoids the extra allocation and just writes directly to the buffer. But strconv.Format{I,Ui}nt is quite fast, especially since it has a fast path for "small integers". Here's a trivial benchmark of all 3 options: ``` $ benchstat fmt.txt goos: darwin goarch: arm64 pkg: x │ fmt.txt │ │ sec/op │ FormatInt/Sprintf-10 55.51n ± 1% FormatInt/Fprintf-10 50.76n ± 1% FormatInt/FormatInt-10 17.16n ± 2% geomean 36.43n │ fmt.txt │ │ B/op │ FormatInt/Sprintf-10 16.00 ± 0% FormatInt/Fprintf-10 8.000 ± 0% FormatInt/FormatInt-10 8.000 ± 0% geomean 10.08 │ fmt.txt │ │ allocs/op │ FormatInt/Sprintf-10 2.000 ± 0% FormatInt/Fprintf-10 1.000 ± 0% FormatInt/FormatInt-10 1.000 ± 0% geomean 1.260 ``` So each %d within a format string is considerably faster. This obviously scales linearly with the number of %d's there are. Signed-off-by: Matt Robenolt <matt@ydekproductions.com>
1 parent 979c1e6 commit 4cc46b0

File tree

2 files changed

+72
-3
lines changed

2 files changed

+72
-3
lines changed

go/vt/sqlparser/tracked_buffer.go

Lines changed: 40 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ package sqlparser
1818

1919
import (
2020
"fmt"
21+
"strconv"
2122
"strings"
2223
)
2324

@@ -211,7 +212,32 @@ func (buf *TrackedBuffer) astPrintf(currentNode SQLNode, format string, values .
211212
}
212213
}
213214
case 'd':
214-
buf.WriteString(fmt.Sprintf("%d", values[fieldnum]))
215+
switch v := values[fieldnum].(type) {
216+
case int:
217+
buf.WriteInt(int64(v))
218+
case int8:
219+
buf.WriteInt(int64(v))
220+
case int16:
221+
buf.WriteInt(int64(v))
222+
case int32:
223+
buf.WriteInt(int64(v))
224+
case int64:
225+
buf.WriteInt(v)
226+
case uint:
227+
buf.WriteUint(uint64(v))
228+
case uint8:
229+
buf.WriteUint(uint64(v))
230+
case uint16:
231+
buf.WriteUint(uint64(v))
232+
case uint32:
233+
buf.WriteUint(uint64(v))
234+
case uint64:
235+
buf.WriteUint(v)
236+
case uintptr:
237+
buf.WriteUint(uint64(v))
238+
default:
239+
panic(fmt.Sprintf("unexepcted TrackedBuffer type %T", v))
240+
}
215241
case 'a':
216242
buf.WriteArg("", values[fieldnum].(string))
217243
default:
@@ -288,14 +314,26 @@ func areBothISExpr(op Expr, val Expr) bool {
288314
// WriteArg writes a value argument into the buffer along with
289315
// tracking information for future substitutions.
290316
func (buf *TrackedBuffer) WriteArg(prefix, arg string) {
317+
length := len(prefix) + len(arg)
291318
buf.bindLocations = append(buf.bindLocations, BindLocation{
292319
Offset: buf.Len(),
293-
Length: len(prefix) + len(arg),
320+
Length: length,
294321
})
322+
buf.Grow(length)
295323
buf.WriteString(prefix)
296324
buf.WriteString(arg)
297325
}
298326

327+
// WriteInt writes a signed integer into the buffer.
328+
func (buf *TrackedBuffer) WriteInt(v int64) {
329+
buf.WriteString(strconv.FormatInt(v, 10))
330+
}
331+
332+
// WriteUint writes an unsigned integer into the buffer.
333+
func (buf *TrackedBuffer) WriteUint(v uint64) {
334+
buf.WriteString(strconv.FormatUint(v, 10))
335+
}
336+
299337
// ParsedQuery returns a ParsedQuery that contains bind
300338
// locations for easy substitution.
301339
func (buf *TrackedBuffer) ParsedQuery() *ParsedQuery {
@@ -335,7 +373,6 @@ func UnescapedString(node SQLNode) string {
335373
buf.SetEscapeNoIdentifier()
336374
node.Format(buf)
337375
return buf.String()
338-
339376
}
340377

341378
// CanonicalString returns a canonical string representation of an SQLNode where all identifiers

go/vt/sqlparser/tracked_buffer_test.go

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -295,3 +295,35 @@ func TestCanonicalOutput(t *testing.T) {
295295
})
296296
}
297297
}
298+
299+
func TestTrackedBufferMyprintf(t *testing.T) {
300+
testcases := []struct {
301+
input string
302+
output string
303+
args []any
304+
}{
305+
{
306+
input: "nothing",
307+
output: "nothing",
308+
args: []any{},
309+
},
310+
{
311+
input: "my name is %s",
312+
output: "my name is Homer",
313+
args: []any{"Homer"},
314+
},
315+
{
316+
input: "%d %d %d %d %d %d %d %d %d %d %d",
317+
output: "1 2 3 4 5 6 7 8 9 10 11",
318+
args: []any{int(1), int8(2), int16(3), int32(4), int64(5), uint(6), uint8(7), uint16(8), uint32(9), uint64(10), uintptr(11)},
319+
},
320+
}
321+
for _, tc := range testcases {
322+
t.Run(tc.input, func(t *testing.T) {
323+
buf := NewTrackedBuffer(nil)
324+
buf.Myprintf(tc.input, tc.args...)
325+
got := buf.String()
326+
assert.Equal(t, tc.output, got)
327+
})
328+
}
329+
}

0 commit comments

Comments
 (0)