fmt: simplify buffer write methods and adjust calls to them
Once upon a time fmt did use bytes.Buffer for its buffer.
The buffer write methods still mimic the bytes.Buffer signatures.
The current code depends on manipulating the buffer []bytes array directly
which makes going back to bytes.Buffer by only changing the type of buffer
impossible. Since type buffer is not exported the methods can be simplified
to the needs of fmt. This saves space and avoids unnecessary overhead.
Use WriteString instead of Write for known inputs since
WriteString is faster than Write to append the same data.
This also saves space in the binary.
Remove the add method from Printer and depending on the data to be written
use WriteRune or WriteByte directly instead.
In total makes the go binary around 4 kilobyte smaller.
name old time/op new time/op delta
SprintfEmpty-2 24.1ns ± 3% 23.8ns ± 1% -1.14% (p=0.000 n=20+20)
SprintfString-2 114ns ± 2% 114ns ± 4% ~ (p=0.558 n=20+19)
SprintfInt-2 116ns ± 9% 118ns ± 7% ~ (p=0.086 n=20+20)
SprintfIntInt-2 195ns ± 6% 193ns ± 5% ~ (p=0.345 n=20+19)
SprintfPrefixedInt-2 251ns ±16% 241ns ± 9% -3.69% (p=0.024 n=20+19)
SprintfFloat-2 203ns ± 4% 205ns ± 5% ~ (p=0.153 n=20+20)
SprintfBoolean-2 101ns ± 7% 96ns ±11% -5.23% (p=0.005 n=19+20)
ManyArgs-2 651ns ± 7% 628ns ± 7% -3.44% (p=0.002 n=20+20)
FprintInt-2 164ns ± 2% 158ns ± 2% -3.62% (p=0.000 n=20+18)
FprintfBytes-2 215ns ± 1% 216ns ± 1% +0.58% (p=0.000 n=20+20)
FprintIntNoAlloc-2 115ns ± 0% 112ns ± 0% -2.61% (p=0.000 n=20+20)
ScanInts-2 700µs ± 0% 702µs ± 1% +0.38% (p=0.000 n=18+20)
ScanRecursiveInt-2 82.7ms ± 0% 82.7ms ± 0% ~ (p=0.820 n=20+20)
Change-Id: I0409eb170b8a26d9f4eb271f6292e5d39faf2d8b
Reviewed-on: https://go-review.googlesource.com/19955
Reviewed-by: Rob Pike <r@golang.org>
diff --git a/src/fmt/fmt_test.go b/src/fmt/fmt_test.go
index 14d3aaf..05187af 100644
--- a/src/fmt/fmt_test.go
+++ b/src/fmt/fmt_test.go
@@ -943,6 +943,13 @@
}
})
}
+func BenchmarkSprintfBoolean(b *testing.B) {
+ b.RunParallel(func(pb *testing.PB) {
+ for pb.Next() {
+ Sprintf("%t", true)
+ }
+ })
+}
func BenchmarkManyArgs(b *testing.B) {
b.RunParallel(func(pb *testing.PB) {
diff --git a/src/fmt/format.go b/src/fmt/format.go
index a4aa897..f7ac047 100644
--- a/src/fmt/format.go
+++ b/src/fmt/format.go
@@ -124,17 +124,12 @@
}
}
-var (
- trueBytes = []byte("true")
- falseBytes = []byte("false")
-)
-
// fmt_boolean formats a boolean.
func (f *fmt) fmt_boolean(v bool) {
if v {
- f.pad(trueBytes)
+ f.padString("true")
} else {
- f.pad(falseBytes)
+ f.padString("false")
}
}
@@ -511,5 +506,5 @@
f.space = oldSpace
f.plus = oldPlus
f.wid = oldWid
- f.buf.Write(irparenBytes)
+ f.buf.WriteString("i)")
}
diff --git a/src/fmt/print.go b/src/fmt/print.go
index 0354d6e..b59599d 100644
--- a/src/fmt/print.go
+++ b/src/fmt/print.go
@@ -13,24 +13,24 @@
"unicode/utf8"
)
-// Some constants in the form of bytes, to avoid string overhead.
-// Needlessly fastidious, I suppose.
-var (
- commaSpaceBytes = []byte(", ")
- nilAngleBytes = []byte("<nil>")
- nilParenBytes = []byte("(nil)")
- nilBytes = []byte("nil")
- mapBytes = []byte("map[")
- percentBangBytes = []byte("%!")
- missingBytes = []byte("(MISSING)")
- badIndexBytes = []byte("(BADINDEX)")
- panicBytes = []byte("(PANIC=")
- extraBytes = []byte("%!(EXTRA ")
- irparenBytes = []byte("i)")
- bytesBytes = []byte("[]byte{")
- badWidthBytes = []byte("%!(BADWIDTH)")
- badPrecBytes = []byte("%!(BADPREC)")
- noVerbBytes = []byte("%!(NOVERB)")
+// Strings for use with buffer.WriteString.
+// This is less overhead than using buffer.Write with byte arrays.
+const (
+ commaSpaceString = ", "
+ nilAngleString = "<nil>"
+ nilParenString = "(nil)"
+ nilString = "nil"
+ mapString = "map["
+ percentBangString = "%!"
+ missingString = "(MISSING)"
+ badIndexString = "(BADINDEX)"
+ panicString = "(PANIC="
+ extraString = "%!(EXTRA "
+ bytesString = "[]byte{"
+ badWidthString = "%!(BADWIDTH)"
+ badPrecString = "%!(BADPREC)"
+ noVerbString = "%!(NOVERB)"
+ invReflectString = "<invalid reflect.Value>"
)
// State represents the printer state passed to custom formatters.
@@ -75,25 +75,22 @@
// Use simple []byte instead of bytes.Buffer to avoid large dependency.
type buffer []byte
-func (b *buffer) Write(p []byte) (n int, err error) {
+func (b *buffer) Write(p []byte) {
*b = append(*b, p...)
- return len(p), nil
}
-func (b *buffer) WriteString(s string) (n int, err error) {
+func (b *buffer) WriteString(s string) {
*b = append(*b, s...)
- return len(s), nil
}
-func (b *buffer) WriteByte(c byte) error {
+func (b *buffer) WriteByte(c byte) {
*b = append(*b, c)
- return nil
}
-func (bp *buffer) WriteRune(r rune) error {
+func (bp *buffer) WriteRune(r rune) {
if r < utf8.RuneSelf {
*bp = append(*bp, byte(r))
- return nil
+ return
}
b := *bp
@@ -103,7 +100,6 @@
}
w := utf8.EncodeRune(b[n:n+utf8.UTFMax], r)
*bp = b[:n+w]
- return nil
}
type pp struct {
@@ -169,14 +165,11 @@
return false
}
-func (p *pp) add(c rune) {
- p.buf.WriteRune(c)
-}
-
// Implement Write so we can call Fprintf on a pp (through State), for
// recursive use in custom verbs.
func (p *pp) Write(b []byte) (ret int, err error) {
- return p.buf.Write(b)
+ p.buf.Write(b)
+ return len(b), nil
}
// These routines end in 'f' and take a format string.
@@ -309,7 +302,7 @@
func (p *pp) unknownType(v reflect.Value) {
if !v.IsValid() {
- p.buf.Write(nilAngleBytes)
+ p.buf.WriteString(nilAngleString)
return
}
p.buf.WriteByte('?')
@@ -319,23 +312,22 @@
func (p *pp) badVerb(verb rune) {
p.erroring = true
- p.add('%')
- p.add('!')
- p.add(verb)
- p.add('(')
+ p.buf.WriteString(percentBangString)
+ p.buf.WriteRune(verb)
+ p.buf.WriteByte('(')
switch {
case p.arg != nil:
p.buf.WriteString(reflect.TypeOf(p.arg).String())
- p.add('=')
+ p.buf.WriteByte('=')
p.printArg(p.arg, 'v', 0)
case p.value.IsValid():
p.buf.WriteString(p.value.Type().String())
- p.add('=')
+ p.buf.WriteByte('=')
p.printValue(p.value, 'v', 0)
default:
- p.buf.Write(nilAngleBytes)
+ p.buf.WriteString(nilAngleString)
}
- p.add(')')
+ p.buf.WriteByte(')')
p.erroring = false
}
@@ -538,12 +530,12 @@
p.buf.WriteString("[]byte(nil)")
} else {
p.buf.WriteString(typ.String())
- p.buf.Write(nilParenBytes)
+ p.buf.WriteString(nilParenString)
}
return
}
if typ == nil {
- p.buf.Write(bytesBytes)
+ p.buf.WriteString(bytesString)
} else {
p.buf.WriteString(typ.String())
p.buf.WriteByte('{')
@@ -554,7 +546,7 @@
for i, c := range v {
if i > 0 {
if p.fmt.sharpV {
- p.buf.Write(commaSpaceBytes)
+ p.buf.WriteString(commaSpaceString)
} else {
p.buf.WriteByte(' ')
}
@@ -605,18 +597,17 @@
}
if p.fmt.sharpV {
- p.add('(')
+ p.buf.WriteByte('(')
p.buf.WriteString(value.Type().String())
- p.add(')')
- p.add('(')
+ p.buf.WriteString(")(")
if u == 0 {
- p.buf.Write(nilBytes)
+ p.buf.WriteString(nilString)
} else {
p.fmt0x64(uint64(u), true)
}
- p.add(')')
+ p.buf.WriteByte(')')
} else if verb == 'v' && u == 0 {
- p.buf.Write(nilAngleBytes)
+ p.buf.WriteString(nilAngleString)
} else {
if use0x64 {
p.fmt0x64(uint64(u), !p.fmt.sharp)
@@ -637,7 +628,7 @@
// Stringer that fails to guard against nil or a nil pointer for a
// value receiver, and in either case, "<nil>" is a nice result.
if v := reflect.ValueOf(arg); v.Kind() == reflect.Ptr && v.IsNil() {
- p.buf.Write(nilAngleBytes)
+ p.buf.WriteString(nilAngleString)
return
}
// Otherwise print a concise panic message. Most of the time the panic
@@ -647,9 +638,9 @@
panic(err)
}
p.fmt.clearflags() // We are done, and for this output we want default behavior.
- p.buf.Write(percentBangBytes)
- p.add(verb)
- p.buf.Write(panicBytes)
+ p.buf.WriteString(percentBangString)
+ p.buf.WriteRune(verb)
+ p.buf.WriteString(panicString)
p.panicking = true
p.printArg(err, 'v', 0)
p.panicking = false
@@ -741,7 +732,7 @@
if arg == nil {
if verb == 'T' || verb == 'v' {
- p.fmt.pad(nilAngleBytes)
+ p.fmt.padString(nilAngleString)
} else {
p.badVerb(verb)
}
@@ -817,7 +808,7 @@
func (p *pp) printValue(value reflect.Value, verb rune, depth int) (wasString bool) {
if !value.IsValid() {
if verb == 'T' || verb == 'v' {
- p.buf.Write(nilAngleBytes)
+ p.buf.WriteString(nilAngleString)
} else {
p.badVerb(verb)
}
@@ -858,7 +849,7 @@
BigSwitch:
switch f := value; f.Kind() {
case reflect.Invalid:
- p.buf.WriteString("<invalid reflect.Value>")
+ p.buf.WriteString(invReflectString)
case reflect.Bool:
p.fmtBool(f.Bool(), verb)
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
@@ -883,18 +874,18 @@
if p.fmt.sharpV {
p.buf.WriteString(f.Type().String())
if f.IsNil() {
- p.buf.WriteString("(nil)")
+ p.buf.WriteString(nilParenString)
break
}
p.buf.WriteByte('{')
} else {
- p.buf.Write(mapBytes)
+ p.buf.WriteString(mapString)
}
keys := f.MapKeys()
for i, key := range keys {
if i > 0 {
if p.fmt.sharpV {
- p.buf.Write(commaSpaceBytes)
+ p.buf.WriteString(commaSpaceString)
} else {
p.buf.WriteByte(' ')
}
@@ -912,13 +903,13 @@
if p.fmt.sharpV {
p.buf.WriteString(value.Type().String())
}
- p.add('{')
+ p.buf.WriteByte('{')
v := f
t := v.Type()
for i := 0; i < v.NumField(); i++ {
if i > 0 {
if p.fmt.sharpV {
- p.buf.Write(commaSpaceBytes)
+ p.buf.WriteString(commaSpaceString)
} else {
p.buf.WriteByte(' ')
}
@@ -937,9 +928,9 @@
if !value.IsValid() {
if p.fmt.sharpV {
p.buf.WriteString(f.Type().String())
- p.buf.Write(nilParenBytes)
+ p.buf.WriteString(nilParenString)
} else {
- p.buf.Write(nilAngleBytes)
+ p.buf.WriteString(nilAngleString)
}
} else {
wasString = p.printValue(value, verb, depth+1)
@@ -982,7 +973,7 @@
for i := 0; i < f.Len(); i++ {
if i > 0 {
if p.fmt.sharpV {
- p.buf.Write(commaSpaceBytes)
+ p.buf.WriteString(commaSpaceString)
} else {
p.buf.WriteByte(' ')
}
@@ -1149,7 +1140,7 @@
p.fmt.wid, p.fmt.widPresent, argNum = intFromArg(a, argNum)
if !p.fmt.widPresent {
- p.buf.Write(badWidthBytes)
+ p.buf.WriteString(badWidthString)
}
// We have a negative width, so take its value and ensure
@@ -1182,7 +1173,7 @@
p.fmt.precPresent = false
}
if !p.fmt.precPresent {
- p.buf.Write(badPrecBytes)
+ p.buf.WriteString(badPrecString)
}
afterIndex = false
} else {
@@ -1199,7 +1190,7 @@
}
if i >= end {
- p.buf.Write(noVerbBytes)
+ p.buf.WriteString(noVerbString)
continue
}
c, w := utf8.DecodeRuneInString(format[i:])
@@ -1210,14 +1201,14 @@
continue
}
if !p.goodArgNum {
- p.buf.Write(percentBangBytes)
- p.add(c)
- p.buf.Write(badIndexBytes)
+ p.buf.WriteString(percentBangString)
+ p.buf.WriteRune(c)
+ p.buf.WriteString(badIndexString)
continue
} else if argNum >= len(a) { // out of operands
- p.buf.Write(percentBangBytes)
- p.add(c)
- p.buf.Write(missingBytes)
+ p.buf.WriteString(percentBangString)
+ p.buf.WriteRune(c)
+ p.buf.WriteString(missingString)
continue
}
arg := a[argNum]
@@ -1248,7 +1239,7 @@
// out of order, in which case it's too expensive to detect if they've all
// been used and arguably OK if they're not.
if !p.reordered && argNum < len(a) {
- p.buf.Write(extraBytes)
+ p.buf.WriteString(extraString)
for ; argNum < len(a); argNum++ {
arg := a[argNum]
if arg != nil {
@@ -1257,7 +1248,7 @@
}
p.printArg(arg, 'v', 0)
if argNum+1 < len(a) {
- p.buf.Write(commaSpaceBytes)
+ p.buf.WriteString(commaSpaceString)
}
}
p.buf.WriteByte(')')
diff --git a/src/fmt/scan.go b/src/fmt/scan.go
index 99cb1af..fa63e49 100644
--- a/src/fmt/scan.go
+++ b/src/fmt/scan.go
@@ -839,7 +839,7 @@
return string(s.buf)
case '"':
// Double-quoted: Include the quotes and let strconv.Unquote do the backslash escapes.
- s.buf.WriteRune(quote)
+ s.buf.WriteByte('"')
for {
r := s.mustReadRune()
s.buf.WriteRune(r)