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 {
{"%x", "abc", "616263"},
{"%x", "\xff\xf0\x0f\xff", "fff00fff"},
{"%X", "\xff\xf0\x0f\xff", "FFF00FFF"},
{"%x", "", ""},
{"% x", "", ""},
{"%#x", "", ""},
{"%# x", "", ""},
{"%x", "xyz", "78797a"},
{"%X", "xyz", "78797A"},
{"% x", "xyz", "78 79 7a"},
......@@ -156,6 +160,10 @@ var fmtTests = []struct {
{"%x", []byte("abc"), "616263"},
{"%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"), "78 79 7a"},
......@@ -204,15 +212,15 @@ var fmtTests = []struct {
{"%.10s", "日本語日本語", "日本語日本語"},
{"%.5s", []byte("日本語日本語"), "日本語日本"},
{"%.5q", "abcdefghijklmnopqrstuvwxyz", `"abcde"`},
{"%.5x", "abcdefghijklmnopqrstuvwxyz", `6162636465`},
{"%.5x", "abcdefghijklmnopqrstuvwxyz", "6162636465"},
{"%.5q", []byte("abcdefghijklmnopqrstuvwxyz"), `"abcde"`},
{"%.5x", []byte("abcdefghijklmnopqrstuvwxyz"), `6162636465`},
{"%.5x", []byte("abcdefghijklmnopqrstuvwxyz"), "6162636465"},
{"%.3q", "日本語日本語", `"日本語"`},
{"%.3q", []byte("日本語日本語"), `"日本語"`},
{"%.1q", "日本語", `"日"`},
{"%.1q", []byte("日本語"), `"日"`},
{"%.1x", "日本語", `e6`},
{"%.1X", []byte("日本語"), `E6`},
{"%.1x", "日本語", "e6"},
{"%.1X", []byte("日本語"), "E6"},
{"%10.1q", "日本語日本語", ` "日"`},
{"%3c", '⌘', " ⌘"},
{"%5q", '\u2026', ` '…'`},
......@@ -471,30 +479,61 @@ var fmtTests = []struct {
{"%q", []string{"a", "b"}, `["a" "b"]`},
{"% 02x", []byte{1}, "01"},
{"% 02x", []byte{1, 2, 3}, "01 02 03"},
// Padding with byte slices.
{"%x", []byte{}, ""},
{"%02x", []byte{}, "00"},
{"%2x", []byte{}, " "},
{"%#2x", []byte{}, " "},
{"% 02x", []byte{}, "00"},
{"%08x", []byte{0xab}, "000000ab"},
{"% 08x", []byte{0xab}, "000000ab"},
{"%08x", []byte{0xab, 0xcd}, "0000abcd"},
{"% 08x", []byte{0xab, 0xcd}, "000ab cd"},
{"%# 02x", []byte{}, "00"},
{"%-2x", []byte{}, " "},
{"%-02x", []byte{}, " "},
{"%8x", []byte{0xab}, " ab"},
{"% 8x", []byte{0xab}, " ab"},
{"%8x", []byte{0xab, 0xcd}, " abcd"},
{"% 8x", []byte{0xab, 0xcd}, " ab cd"},
{"%#8x", []byte{0xab}, " 0xab"},
{"%# 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
{"%x", "", ""},
{"%02x", "", "00"},
{"%2x", "", " "},
{"%#2x", "", " "},
{"% 02x", "", "00"},
{"%08x", "\xab", "000000ab"},
{"% 08x", "\xab", "000000ab"},
{"%08x", "\xab\xcd", "0000abcd"},
{"% 08x", "\xab\xcd", "000ab cd"},
{"%# 02x", "", "00"},
{"%-2x", "", " "},
{"%-02x", "", " "},
{"%8x", "\xab", " ab"},
{"% 8x", "\xab", " ab"},
{"%8x", "\xab\xcd", " abcd"},
{"% 8x", "\xab\xcd", " ab cd"},
{"%#8x", "\xab", " 0xab"},
{"%# 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
{"%v", renamedBool(true), "true"},
......@@ -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) {
b.RunParallel(func(pb *testing.PB) {
var buf bytes.Buffer
......
......@@ -14,8 +14,8 @@ const (
// Hex can add 0x and we handle it specially.
nByte = 65
ldigits = "0123456789abcdef"
udigits = "0123456789ABCDEF"
ldigits = "0123456789abcdefx"
udigits = "0123456789ABCDEFX"
)
const (
......@@ -236,8 +236,9 @@ func (f *fmt) integer(a int64, base uint64, signedness bool, digits string) {
buf[i] = '0'
}
case 16:
// Add a leading 0x or 0X.
i--
buf[i] = 'x' + digits[10] - 'a'
buf[i] = digits[16]
i--
buf[i] = '0'
}
......@@ -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.
func (f *fmt) fmt_sbx(s string, b []byte, digits string) {
n := len(b)
length := len(b)
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
}
// Elements will be separated by a space.
width += length - 1
} else if f.sharp {
// Only a leading 0x or 0X will be added for the whole string.
width += 2
}
} else { // The byte slice or string that should be encoded is empty.
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])
}
x := digits[10] - 'a' + 'x'
// TODO: Avoid buffer by pre-padding.
var buf []byte
for i := 0; i < n; i++ {
if i > 0 && f.space {
var c byte
for i := 0; i < length; 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 f.sharp && (f.space || i == 0) {
buf = append(buf, '0', x)
}
var c byte
if b == nil {
c = s[i]
if b != nil {
c = b[i] // Take a byte from the input byte slice.
} 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])
}
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.
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)
}
// fmt_bx formats a byte slice as a hexadecimal encoding of its bytes.
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)
}
......
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