From: Martin Möhrmann Date: Sat, 27 Feb 2016 18:47:43 +0000 (+0100) Subject: fmt: optimize %x and %X formatting for byte slices and strings X-Git-Tag: go1.7beta1~1581 X-Git-Url: http://www.git.cypherpunks.su/?a=commitdiff_plain;h=033e3e106e6132b530759bf97f7b359b9897ae25;p=gostls13.git 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 TryBot-Result: Gobot Gobot Reviewed-by: Rob Pike --- diff --git a/src/fmt/fmt_test.go b/src/fmt/fmt_test.go index 69141a156d..47486c4586 100644 --- a/src/fmt/fmt_test.go +++ b/src/fmt/fmt_test.go @@ -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 diff --git a/src/fmt/format.go b/src/fmt/format.go index 302f82441d..0388d4764c 100644 --- a/src/fmt/format.go +++ b/src/fmt/format.go @@ -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) }