mirror of
https://github.com/golang/go
synced 2024-11-13 19:00:25 -07:00
fmt: avoid allocation when formatting byte slice arguments with verb s
fmtBytes is in the top 10 callers of runtime.slicebytetostring according to Google wide profiling data. Avoid the string conversion of the input byte slice in fmtBytes by calling a newly added specialized fmtS function for byte slices. Expand tests for verb s with widths to test strings and byte slice arguments. SprintfTruncateString 157ns ± 4% 156ns ± 3% ~ (p=0.122 n=20+20) SprintfTruncateBytes 188ns ± 2% 155ns ± 3% -18.00% (p=0.000 n=20+19) name old alloc/op new alloc/op delta SprintfTruncateString 16.0B ± 0% 16.0B ± 0% ~ (all equal) SprintfTruncateBytes 64.0B ± 0% 16.0B ± 0% -75.00% (p=0.000 n=20+20) name old allocs/op new allocs/op delta SprintfTruncateString 1.00 ± 0% 1.00 ± 0% ~ (all equal) SprintfTruncateBytes 2.00 ± 0% 1.00 ± 0% -50.00% (p=0.000 n=20+20) Change-Id: I461bf514d4232b39bd9c812f7faa4e5ef693a03b Reviewed-on: https://go-review.googlesource.com/c/145284 Run-TryBot: Martin Möhrmann <martisch@uos.de> TryBot-Result: Gobot Gobot <gobot@golang.org> Reviewed-by: Rob Pike <r@golang.org>
This commit is contained in:
parent
75798e8ada
commit
f36e92dbfc
@ -298,20 +298,30 @@ var fmtTests = []struct {
|
||||
|
||||
// width
|
||||
{"%5s", "abc", " abc"},
|
||||
{"%5s", []byte("abc"), " abc"},
|
||||
{"%2s", "\u263a", " ☺"},
|
||||
{"%2s", []byte("\u263a"), " ☺"},
|
||||
{"%-5s", "abc", "abc "},
|
||||
{"%-8q", "abc", `"abc" `},
|
||||
{"%-5s", []byte("abc"), "abc "},
|
||||
{"%05s", "abc", "00abc"},
|
||||
{"%08q", "abc", `000"abc"`},
|
||||
{"%05s", []byte("abc"), "00abc"},
|
||||
{"%5s", "abcdefghijklmnopqrstuvwxyz", "abcdefghijklmnopqrstuvwxyz"},
|
||||
{"%5s", []byte("abcdefghijklmnopqrstuvwxyz"), "abcdefghijklmnopqrstuvwxyz"},
|
||||
{"%.5s", "abcdefghijklmnopqrstuvwxyz", "abcde"},
|
||||
{"%.5s", []byte("abcdefghijklmnopqrstuvwxyz"), "abcde"},
|
||||
{"%.0s", "日本語日本語", ""},
|
||||
{"%.0s", []byte("日本語日本語"), ""},
|
||||
{"%.5s", "日本語日本語", "日本語日本"},
|
||||
{"%.10s", "日本語日本語", "日本語日本語"},
|
||||
{"%.5s", []byte("日本語日本語"), "日本語日本"},
|
||||
{"%.10s", "日本語日本語", "日本語日本語"},
|
||||
{"%.10s", []byte("日本語日本語"), "日本語日本語"},
|
||||
{"%08q", "abc", `000"abc"`},
|
||||
{"%08q", []byte("abc"), `000"abc"`},
|
||||
{"%-8q", "abc", `"abc" `},
|
||||
{"%-8q", []byte("abc"), `"abc" `},
|
||||
{"%.5q", "abcdefghijklmnopqrstuvwxyz", `"abcde"`},
|
||||
{"%.5x", "abcdefghijklmnopqrstuvwxyz", "6162636465"},
|
||||
{"%.5q", []byte("abcdefghijklmnopqrstuvwxyz"), `"abcde"`},
|
||||
{"%.5x", "abcdefghijklmnopqrstuvwxyz", "6162636465"},
|
||||
{"%.5x", []byte("abcdefghijklmnopqrstuvwxyz"), "6162636465"},
|
||||
{"%.3q", "日本語日本語", `"日本語"`},
|
||||
{"%.3q", []byte("日本語日本語"), `"日本語"`},
|
||||
@ -320,6 +330,7 @@ var fmtTests = []struct {
|
||||
{"%.1x", "日本語", "e6"},
|
||||
{"%.1X", []byte("日本語"), "E6"},
|
||||
{"%10.1q", "日本語日本語", ` "日"`},
|
||||
{"%10.1q", []byte("日本語日本語"), ` "日"`},
|
||||
{"%10v", nil, " <nil>"},
|
||||
{"%-10v", nil, "<nil> "},
|
||||
|
||||
@ -1211,7 +1222,16 @@ func BenchmarkSprintfString(b *testing.B) {
|
||||
func BenchmarkSprintfTruncateString(b *testing.B) {
|
||||
b.RunParallel(func(pb *testing.PB) {
|
||||
for pb.Next() {
|
||||
Sprintf("%.3s", "日本語日本語日本語")
|
||||
Sprintf("%.3s", "日本語日本語日本語日本語")
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func BenchmarkSprintfTruncateBytes(b *testing.B) {
|
||||
var bytes interface{} = []byte("日本語日本語日本語日本語")
|
||||
b.RunParallel(func(pb *testing.PB) {
|
||||
for pb.Next() {
|
||||
Sprintf("%.3s", bytes)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
@ -308,8 +308,8 @@ func (f *fmt) fmtInteger(u uint64, base int, isSigned bool, digits string) {
|
||||
f.zero = oldZero
|
||||
}
|
||||
|
||||
// truncate truncates the string to the specified precision, if present.
|
||||
func (f *fmt) truncate(s string) string {
|
||||
// truncate truncates the string s to the specified precision, if present.
|
||||
func (f *fmt) truncateString(s string) string {
|
||||
if f.precPresent {
|
||||
n := f.prec
|
||||
for i := range s {
|
||||
@ -322,12 +322,37 @@ func (f *fmt) truncate(s string) string {
|
||||
return s
|
||||
}
|
||||
|
||||
// truncate truncates the byte slice b as a string of the specified precision, if present.
|
||||
func (f *fmt) truncate(b []byte) []byte {
|
||||
if f.precPresent {
|
||||
n := f.prec
|
||||
for i := 0; i < len(b); {
|
||||
n--
|
||||
if n < 0 {
|
||||
return b[:i]
|
||||
}
|
||||
wid := 1
|
||||
if b[i] >= utf8.RuneSelf {
|
||||
_, wid = utf8.DecodeRune(b[i:])
|
||||
}
|
||||
i += wid
|
||||
}
|
||||
}
|
||||
return b
|
||||
}
|
||||
|
||||
// fmtS formats a string.
|
||||
func (f *fmt) fmtS(s string) {
|
||||
s = f.truncate(s)
|
||||
s = f.truncateString(s)
|
||||
f.padString(s)
|
||||
}
|
||||
|
||||
// fmtBs formats the byte slice b as if it was formatted as string with fmtS.
|
||||
func (f *fmt) fmtBs(b []byte) {
|
||||
b = f.truncate(b)
|
||||
f.pad(b)
|
||||
}
|
||||
|
||||
// fmtSbx formats a string or byte slice as a hexadecimal encoding of its bytes.
|
||||
func (f *fmt) fmtSbx(s string, b []byte, digits string) {
|
||||
length := len(b)
|
||||
@ -408,7 +433,7 @@ func (f *fmt) fmtBx(b []byte, digits string) {
|
||||
// If f.sharp is set a raw (backquoted) string may be returned instead
|
||||
// if the string does not contain any control characters other than tab.
|
||||
func (f *fmt) fmtQ(s string) {
|
||||
s = f.truncate(s)
|
||||
s = f.truncateString(s)
|
||||
if f.sharp && strconv.CanBackquote(s) {
|
||||
f.padString("`" + s + "`")
|
||||
return
|
||||
|
@ -488,7 +488,7 @@ func (p *pp) fmtBytes(v []byte, verb rune, typeString string) {
|
||||
p.buf.WriteByte(']')
|
||||
}
|
||||
case 's':
|
||||
p.fmt.fmtS(string(v))
|
||||
p.fmt.fmtBs(v)
|
||||
case 'x':
|
||||
p.fmt.fmtBx(v, ldigits)
|
||||
case 'X':
|
||||
|
Loading…
Reference in New Issue
Block a user