Commit 033e3e10 authored by Martin Möhrmann's avatar Martin Möhrmann Committed by Rob Pike

fmt: optimize %x and %X formatting for byte slices and strings

No extra buffering is needed to save the encoding
since the left padding can be computed and written out
before the encoding is generated.

Add extra tests to both string and byte slice formatting.

name                old time/op  new time/op  delta
SprintfHexString-2   410ns ± 3%   194ns ± 3%  -52.60%  (p=0.000 n=20+19)
SprintfHexBytes-2    431ns ± 3%   202ns ± 2%  -53.13%  (p=0.000 n=18+20)

Change-Id: Ibca4316427c89f834e4faee61614493c7eedb42b
Reviewed-on: https://go-review.googlesource.com/20097
Run-TryBot: Rob Pike <r@golang.org>
TryBot-Result: Gobot Gobot <gobot@golang.org>
Reviewed-by: default avatarRob Pike <r@golang.org>
parent 71d13a8c
...@@ -141,6 +141,10 @@ var fmtTests = []struct { ...@@ -141,6 +141,10 @@ var fmtTests = []struct {
{"%x", "abc", "616263"}, {"%x", "abc", "616263"},
{"%x", "\xff\xf0\x0f\xff", "fff00fff"}, {"%x", "\xff\xf0\x0f\xff", "fff00fff"},
{"%X", "\xff\xf0\x0f\xff", "FFF00FFF"}, {"%X", "\xff\xf0\x0f\xff", "FFF00FFF"},
{"%x", "", ""},
{"% x", "", ""},
{"%#x", "", ""},
{"%# x", "", ""},
{"%x", "xyz", "78797a"}, {"%x", "xyz", "78797a"},
{"%X", "xyz", "78797A"}, {"%X", "xyz", "78797A"},
{"% x", "xyz", "78 79 7a"}, {"% x", "xyz", "78 79 7a"},
...@@ -156,6 +160,10 @@ var fmtTests = []struct { ...@@ -156,6 +160,10 @@ var fmtTests = []struct {
{"%x", []byte("abc"), "616263"}, {"%x", []byte("abc"), "616263"},
{"%x", []byte("\xff\xf0\x0f\xff"), "fff00fff"}, {"%x", []byte("\xff\xf0\x0f\xff"), "fff00fff"},
{"%X", []byte("\xff\xf0\x0f\xff"), "FFF00FFF"}, {"%X", []byte("\xff\xf0\x0f\xff"), "FFF00FFF"},
{"%x", []byte(""), ""},
{"% x", []byte(""), ""},
{"%#x", []byte(""), ""},
{"%# x", []byte(""), ""},
{"%x", []byte("xyz"), "78797a"}, {"%x", []byte("xyz"), "78797a"},
{"%X", []byte("xyz"), "78797A"}, {"%X", []byte("xyz"), "78797A"},
{"% x", []byte("xyz"), "78 79 7a"}, {"% x", []byte("xyz"), "78 79 7a"},
...@@ -204,15 +212,15 @@ var fmtTests = []struct { ...@@ -204,15 +212,15 @@ var fmtTests = []struct {
{"%.10s", "日本語日本語", "日本語日本語"}, {"%.10s", "日本語日本語", "日本語日本語"},
{"%.5s", []byte("日本語日本語"), "日本語日本"}, {"%.5s", []byte("日本語日本語"), "日本語日本"},
{"%.5q", "abcdefghijklmnopqrstuvwxyz", `"abcde"`}, {"%.5q", "abcdefghijklmnopqrstuvwxyz", `"abcde"`},
{"%.5x", "abcdefghijklmnopqrstuvwxyz", `6162636465`}, {"%.5x", "abcdefghijklmnopqrstuvwxyz", "6162636465"},
{"%.5q", []byte("abcdefghijklmnopqrstuvwxyz"), `"abcde"`}, {"%.5q", []byte("abcdefghijklmnopqrstuvwxyz"), `"abcde"`},
{"%.5x", []byte("abcdefghijklmnopqrstuvwxyz"), `6162636465`}, {"%.5x", []byte("abcdefghijklmnopqrstuvwxyz"), "6162636465"},
{"%.3q", "日本語日本語", `"日本語"`}, {"%.3q", "日本語日本語", `"日本語"`},
{"%.3q", []byte("日本語日本語"), `"日本語"`}, {"%.3q", []byte("日本語日本語"), `"日本語"`},
{"%.1q", "日本語", `"日"`}, {"%.1q", "日本語", `"日"`},
{"%.1q", []byte("日本語"), `"日"`}, {"%.1q", []byte("日本語"), `"日"`},
{"%.1x", "日本語", `e6`}, {"%.1x", "日本語", "e6"},
{"%.1X", []byte("日本語"), `E6`}, {"%.1X", []byte("日本語"), "E6"},
{"%10.1q", "日本語日本語", ` "日"`}, {"%10.1q", "日本語日本語", ` "日"`},
{"%3c", '⌘', " ⌘"}, {"%3c", '⌘', " ⌘"},
{"%5q", '\u2026', ` '…'`}, {"%5q", '\u2026', ` '…'`},
...@@ -471,30 +479,61 @@ var fmtTests = []struct { ...@@ -471,30 +479,61 @@ var fmtTests = []struct {
{"%q", []string{"a", "b"}, `["a" "b"]`}, {"%q", []string{"a", "b"}, `["a" "b"]`},
{"% 02x", []byte{1}, "01"}, {"% 02x", []byte{1}, "01"},
{"% 02x", []byte{1, 2, 3}, "01 02 03"}, {"% 02x", []byte{1, 2, 3}, "01 02 03"},
// Padding with byte slices. // Padding with byte slices.
{"%x", []byte{}, ""}, {"%2x", []byte{}, " "},
{"%02x", []byte{}, "00"}, {"%#2x", []byte{}, " "},
{"% 02x", []byte{}, "00"}, {"% 02x", []byte{}, "00"},
{"%08x", []byte{0xab}, "000000ab"}, {"%# 02x", []byte{}, "00"},
{"% 08x", []byte{0xab}, "000000ab"}, {"%-2x", []byte{}, " "},
{"%08x", []byte{0xab, 0xcd}, "0000abcd"}, {"%-02x", []byte{}, " "},
{"% 08x", []byte{0xab, 0xcd}, "000ab cd"},
{"%8x", []byte{0xab}, " ab"}, {"%8x", []byte{0xab}, " ab"},
{"% 8x", []byte{0xab}, " ab"}, {"% 8x", []byte{0xab}, " ab"},
{"%8x", []byte{0xab, 0xcd}, " abcd"}, {"%#8x", []byte{0xab}, " 0xab"},
{"% 8x", []byte{0xab, 0xcd}, " ab cd"}, {"%# 8x", []byte{0xab}, " 0xab"},
{"%08x", []byte{0xab}, "000000ab"},
{"% 08x", []byte{0xab}, "000000ab"},
{"%#08x", []byte{0xab}, "00000xab"},
{"%# 08x", []byte{0xab}, "00000xab"},
{"%10x", []byte{0xab, 0xcd}, " abcd"},
{"% 10x", []byte{0xab, 0xcd}, " ab cd"},
{"%#10x", []byte{0xab, 0xcd}, " 0xabcd"},
{"%# 10x", []byte{0xab, 0xcd}, " 0xab 0xcd"},
{"%010x", []byte{0xab, 0xcd}, "000000abcd"},
{"% 010x", []byte{0xab, 0xcd}, "00000ab cd"},
{"%#010x", []byte{0xab, 0xcd}, "00000xabcd"},
{"%# 010x", []byte{0xab, 0xcd}, "00xab 0xcd"},
{"%-10X", []byte{0xab}, "AB "},
{"% -010X", []byte{0xab}, "AB "},
{"%#-10X", []byte{0xab, 0xcd}, "0XABCD "},
{"%# -010X", []byte{0xab, 0xcd}, "0XAB 0XCD "},
// Same for strings // Same for strings
{"%x", "", ""}, {"%2x", "", " "},
{"%02x", "", "00"}, {"%#2x", "", " "},
{"% 02x", "", "00"}, {"% 02x", "", "00"},
{"%08x", "\xab", "000000ab"}, {"%# 02x", "", "00"},
{"% 08x", "\xab", "000000ab"}, {"%-2x", "", " "},
{"%08x", "\xab\xcd", "0000abcd"}, {"%-02x", "", " "},
{"% 08x", "\xab\xcd", "000ab cd"},
{"%8x", "\xab", " ab"}, {"%8x", "\xab", " ab"},
{"% 8x", "\xab", " ab"}, {"% 8x", "\xab", " ab"},
{"%8x", "\xab\xcd", " abcd"}, {"%#8x", "\xab", " 0xab"},
{"% 8x", "\xab\xcd", " ab cd"}, {"%# 8x", "\xab", " 0xab"},
{"%08x", "\xab", "000000ab"},
{"% 08x", "\xab", "000000ab"},
{"%#08x", "\xab", "00000xab"},
{"%# 08x", "\xab", "00000xab"},
{"%10x", "\xab\xcd", " abcd"},
{"% 10x", "\xab\xcd", " ab cd"},
{"%#10x", "\xab\xcd", " 0xabcd"},
{"%# 10x", "\xab\xcd", " 0xab 0xcd"},
{"%010x", "\xab\xcd", "000000abcd"},
{"% 010x", "\xab\xcd", "00000ab cd"},
{"%#010x", "\xab\xcd", "00000xabcd"},
{"%# 010x", "\xab\xcd", "00xab 0xcd"},
{"%-10X", "\xab", "AB "},
{"% -010X", "\xab", "AB "},
{"%#-10X", "\xab\xcd", "0XABCD "},
{"%# -010X", "\xab\xcd", "0XAB 0XCD "},
// renamings // renamings
{"%v", renamedBool(true), "true"}, {"%v", renamedBool(true), "true"},
...@@ -977,6 +1016,23 @@ func BenchmarkSprintfBoolean(b *testing.B) { ...@@ -977,6 +1016,23 @@ func BenchmarkSprintfBoolean(b *testing.B) {
}) })
} }
func BenchmarkSprintfHexString(b *testing.B) {
b.RunParallel(func(pb *testing.PB) {
for pb.Next() {
Sprintf("% #x", "0123456789abcdef")
}
})
}
func BenchmarkSprintfHexBytes(b *testing.B) {
data := []byte("0123456789abcdef")
b.RunParallel(func(pb *testing.PB) {
for pb.Next() {
Sprintf("% #x", data)
}
})
}
func BenchmarkManyArgs(b *testing.B) { func BenchmarkManyArgs(b *testing.B) {
b.RunParallel(func(pb *testing.PB) { b.RunParallel(func(pb *testing.PB) {
var buf bytes.Buffer var buf bytes.Buffer
......
...@@ -14,8 +14,8 @@ const ( ...@@ -14,8 +14,8 @@ const (
// Hex can add 0x and we handle it specially. // Hex can add 0x and we handle it specially.
nByte = 65 nByte = 65
ldigits = "0123456789abcdef" ldigits = "0123456789abcdefx"
udigits = "0123456789ABCDEF" udigits = "0123456789ABCDEFX"
) )
const ( const (
...@@ -236,8 +236,9 @@ func (f *fmt) integer(a int64, base uint64, signedness bool, digits string) { ...@@ -236,8 +236,9 @@ func (f *fmt) integer(a int64, base uint64, signedness bool, digits string) {
buf[i] = '0' buf[i] = '0'
} }
case 16: case 16:
// Add a leading 0x or 0X.
i-- i--
buf[i] = 'x' + digits[10] - 'a' buf[i] = digits[16]
i-- i--
buf[i] = '0' buf[i] = '0'
} }
...@@ -302,44 +303,77 @@ func (f *fmt) fmt_s(s string) { ...@@ -302,44 +303,77 @@ func (f *fmt) fmt_s(s string) {
// fmt_sbx formats a string or byte slice as a hexadecimal encoding of its bytes. // fmt_sbx formats a string or byte slice as a hexadecimal encoding of its bytes.
func (f *fmt) fmt_sbx(s string, b []byte, digits string) { func (f *fmt) fmt_sbx(s string, b []byte, digits string) {
n := len(b) length := len(b)
if b == nil { if b == nil {
n = len(s) // No byte slice present. Assume string s should be encoded.
length = len(s)
}
// Set length to not process more bytes than the precision demands.
if f.precPresent && f.prec < length {
length = f.prec
}
// Compute width of the encoding taking into account the f.sharp and f.space flag.
width := 2 * length
if width > 0 {
if f.space {
// Each element encoded by two hexadecimals will get a leading 0x or 0X.
if f.sharp {
width *= 2
} }
x := digits[10] - 'a' + 'x' // Elements will be separated by a space.
// TODO: Avoid buffer by pre-padding. width += length - 1
var buf []byte } else if f.sharp {
for i := 0; i < n; i++ { // Only a leading 0x or 0X will be added for the whole string.
if i > 0 && f.space { width += 2
buf = append(buf, ' ')
} }
if f.sharp && (f.space || i == 0) { } else { // The byte slice or string that should be encoded is empty.
buf = append(buf, '0', x) if f.widPresent {
f.writePadding(f.wid)
}
return
}
// Handle padding to the left.
if f.widPresent && f.wid > width && !f.minus {
f.writePadding(f.wid - width)
}
// Write the encoding directly into the output buffer.
buf := *f.buf
if f.sharp {
// Add leading 0x or 0X.
buf = append(buf, '0', digits[16])
} }
var c byte var c byte
if b == nil { for i := 0; i < length; i++ {
c = s[i] if f.space && i > 0 {
// Separate elements with a space.
buf = append(buf, ' ')
if f.sharp {
// Add leading 0x or 0X for each element.
buf = append(buf, '0', digits[16])
}
}
if b != nil {
c = b[i] // Take a byte from the input byte slice.
} else { } else {
c = b[i] c = s[i] // Take a byte from the input string.
} }
// Encode each byte as two hexadecimal digits.
buf = append(buf, digits[c>>4], digits[c&0xF]) buf = append(buf, digits[c>>4], digits[c&0xF])
} }
f.pad(buf) *f.buf = buf
// Handle padding to the right.
if f.widPresent && f.wid > width && f.minus {
f.writePadding(f.wid - width)
}
} }
// fmt_sx formats a string as a hexadecimal encoding of its bytes. // fmt_sx formats a string as a hexadecimal encoding of its bytes.
func (f *fmt) fmt_sx(s, digits string) { func (f *fmt) fmt_sx(s, digits string) {
if f.precPresent && f.prec < len(s) {
s = s[:f.prec]
}
f.fmt_sbx(s, nil, digits) f.fmt_sbx(s, nil, digits)
} }
// fmt_bx formats a byte slice as a hexadecimal encoding of its bytes. // fmt_bx formats a byte slice as a hexadecimal encoding of its bytes.
func (f *fmt) fmt_bx(b []byte, digits string) { func (f *fmt) fmt_bx(b []byte, digits string) {
if f.precPresent && f.prec < len(b) {
b = b[:f.prec]
}
f.fmt_sbx("", b, digits) f.fmt_sbx("", b, digits)
} }
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment