]> Cypherpunks repositories - gostls13.git/commitdiff
fmt: avoid allocation when formatting byte slice arguments with verb s
authorMartin Möhrmann <moehrmann@google.com>
Sun, 28 Oct 2018 16:28:04 +0000 (17:28 +0100)
committerMartin Möhrmann <martisch@uos.de>
Wed, 14 Nov 2018 20:32:15 +0000 (20:32 +0000)
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>
src/fmt/fmt_test.go
src/fmt/format.go
src/fmt/print.go

index e97372225c9a886f269a19e29516ea3ae3231830..1907268c748bbab2ebb70405f5da8b657c64e267 100644 (file)
@@ -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)
                }
        })
 }
index 91103f2c07f1804af09fc0cac9fb4282c6c762dd..d6da8aed1e3aad72b5c76f381c00333a0cb4f2a6 100644 (file)
@@ -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 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
index 22dc52ccdcdd3f4f1de7dcfe4acbdbfe8ee7da30..5df34a25e532044b6eb50068fea41ef89e662ab3 100644 (file)
@@ -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':