Skip to content

Commit e5dd05d

Browse files
dsnetpull[bot]
authored andcommitted
time: optimize appendInt and appendNanos
The appendInt function previously performed a double pass over the formatted integer. We can avoid the second pass if we knew the exact length of formatted integer, allowing us to directly serialize into the output buffer. Rename formatNano to appendNano to be consistent with other append-like functionality. Performance: name old time/op new time/op delta FormatRFC3339Nano 109ns ± 1% 72ns ± 1% -34.06% (p=0.000 n=10+10) Change-Id: Id48f77eb4976fb1dcd6e27fb6a02d29cbf0c026a Reviewed-on: https://go-review.googlesource.com/c/go/+/444278 Run-TryBot: Joseph Tsai <[email protected]> TryBot-Result: Gopher Robot <[email protected]> Reviewed-by: Ian Lance Taylor <[email protected]> Reviewed-by: David Chase <[email protected]>
1 parent 3478037 commit e5dd05d

File tree

4 files changed

+100
-38
lines changed

4 files changed

+100
-38
lines changed

src/time/export_test.go

+1
Original file line numberDiff line numberDiff line change
@@ -133,6 +133,7 @@ var StdChunkNames = map[int]string{
133133

134134
var Quote = quote
135135

136+
var AppendInt = appendInt
136137
var AppendFormatAny = Time.appendFormat
137138
var AppendFormatRFC3339 = Time.appendFormatRFC3339
138139
var ParseAny = parse

src/time/format.go

+53-37
Original file line numberDiff line numberDiff line change
@@ -403,24 +403,46 @@ func appendInt(b []byte, x int, width int) []byte {
403403
u = uint(-x)
404404
}
405405

406-
// Assemble decimal in reverse order.
407-
var buf [20]byte
408-
i := len(buf)
409-
for u >= 10 {
410-
i--
411-
q := u / 10
412-
buf[i] = byte('0' + u - q*10)
413-
u = q
406+
// 2-digit and 4-digit fields are the most common in time formats.
407+
utod := func(u uint) byte { return '0' + byte(u) }
408+
switch {
409+
case width == 2 && u < 1e2:
410+
return append(b, utod(u/1e1), utod(u%1e1))
411+
case width == 4 && u < 1e4:
412+
return append(b, utod(u/1e3), utod(u/1e2%1e1), utod(u/1e1%1e1), utod(u%1e1))
413+
}
414+
415+
// Compute the number of decimal digits.
416+
var n int
417+
if u == 0 {
418+
n = 1
419+
}
420+
for u2 := u; u2 > 0; u2 /= 10 {
421+
n++
414422
}
415-
i--
416-
buf[i] = byte('0' + u)
417423

418424
// Add 0-padding.
419-
for w := len(buf) - i; w < width; w++ {
425+
for pad := width - n; pad > 0; pad-- {
420426
b = append(b, '0')
421427
}
422428

423-
return append(b, buf[i:]...)
429+
// Ensure capacity.
430+
if len(b)+n <= cap(b) {
431+
b = b[:len(b)+n]
432+
} else {
433+
b = append(b, make([]byte, n)...)
434+
}
435+
436+
// Assemble decimal in reverse order.
437+
i := len(b) - 1
438+
for u >= 10 && i > 0 {
439+
q := u / 10
440+
b[i] = utod(u - q*10)
441+
u = q
442+
i--
443+
}
444+
b[i] = utod(u)
445+
return b
424446
}
425447

426448
// Never printed, just needs to be non-nil for return by atoi.
@@ -444,7 +466,7 @@ func atoi[bytes []byte | string](s bytes) (x int, err error) {
444466
return x, nil
445467
}
446468

447-
// The "std" value passed to formatNano contains two packed fields: the number of
469+
// The "std" value passed to appendNano contains two packed fields: the number of
448470
// digits after the decimal and the separator character (period or comma).
449471
// These functions pack and unpack that variable.
450472
func stdFracSecond(code, n, c int) int {
@@ -466,35 +488,29 @@ func separator(std int) byte {
466488
return ','
467489
}
468490

469-
// formatNano appends a fractional second, as nanoseconds, to b
470-
// and returns the result.
471-
func formatNano(b []byte, nanosec uint, std int) []byte {
472-
var (
473-
n = digitsLen(std)
474-
separator = separator(std)
475-
trim = std&stdMask == stdFracSecond9
476-
)
477-
u := nanosec
478-
var buf [9]byte
479-
for start := len(buf); start > 0; {
480-
start--
481-
buf[start] = byte(u%10 + '0')
482-
u /= 10
491+
// appendNano appends a fractional second, as nanoseconds, to b
492+
// and returns the result. The nanosec must be within [0, 999999999].
493+
func appendNano(b []byte, nanosec int, std int) []byte {
494+
trim := std&stdMask == stdFracSecond9
495+
n := digitsLen(std)
496+
if trim && (n == 0 || nanosec == 0) {
497+
return b
483498
}
484-
485-
if n > 9 {
486-
n = 9
499+
dot := separator(std)
500+
b = append(b, dot)
501+
b = appendInt(b, nanosec, 9)
502+
if n < 9 {
503+
b = b[:len(b)-9+n]
487504
}
488505
if trim {
489-
for n > 0 && buf[n-1] == '0' {
490-
n--
506+
for len(b) > 0 && b[len(b)-1] == '0' {
507+
b = b[:len(b)-1]
491508
}
492-
if n == 0 {
493-
return b
509+
if len(b) > 0 && b[len(b)-1] == dot {
510+
b = b[:len(b)-1]
494511
}
495512
}
496-
b = append(b, separator)
497-
return append(b, buf[:n]...)
513+
return b
498514
}
499515

500516
// String returns the time formatted using the format string
@@ -791,7 +807,7 @@ func (t Time) appendFormat(b []byte, layout string) []byte {
791807
b = appendInt(b, zone/60, 2)
792808
b = appendInt(b, zone%60, 2)
793809
case stdFracSecond0, stdFracSecond9:
794-
b = formatNano(b, uint(t.Nanosecond()), std)
810+
b = appendNano(b, t.Nanosecond(), std)
795811
}
796812
}
797813
return b

src/time/format_rfc3339.go

+1-1
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,7 @@ func (t Time) appendFormatRFC3339(b []byte, nanos bool) []byte {
3838

3939
if nanos {
4040
std := stdFracSecond(stdFracSecond9, 9, '.')
41-
b = formatNano(b, uint(t.Nanosecond()), std)
41+
b = appendNano(b, t.Nanosecond(), std)
4242
}
4343

4444
if offset == 0 {

src/time/format_test.go

+45
Original file line numberDiff line numberDiff line change
@@ -90,6 +90,51 @@ func TestRFC3339Conversion(t *testing.T) {
9090
}
9191
}
9292

93+
func TestAppendInt(t *testing.T) {
94+
tests := []struct {
95+
in int
96+
width int
97+
want string
98+
}{
99+
{0, 0, "0"},
100+
{0, 1, "0"},
101+
{0, 2, "00"},
102+
{0, 3, "000"},
103+
{1, 0, "1"},
104+
{1, 1, "1"},
105+
{1, 2, "01"},
106+
{1, 3, "001"},
107+
{-1, 0, "-1"},
108+
{-1, 1, "-1"},
109+
{-1, 2, "-01"},
110+
{-1, 3, "-001"},
111+
{99, 2, "99"},
112+
{100, 2, "100"},
113+
{1, 4, "0001"},
114+
{12, 4, "0012"},
115+
{123, 4, "0123"},
116+
{1234, 4, "1234"},
117+
{12345, 4, "12345"},
118+
{1, 5, "00001"},
119+
{12, 5, "00012"},
120+
{123, 5, "00123"},
121+
{1234, 5, "01234"},
122+
{12345, 5, "12345"},
123+
{123456, 5, "123456"},
124+
{0, 9, "000000000"},
125+
{123, 9, "000000123"},
126+
{123456, 9, "000123456"},
127+
{123456789, 9, "123456789"},
128+
}
129+
var got []byte
130+
for _, tt := range tests {
131+
got = AppendInt(got[:0], tt.in, tt.width)
132+
if string(got) != tt.want {
133+
t.Errorf("appendInt(%d, %d) = %s, want %s", tt.in, tt.width, got, tt.want)
134+
}
135+
}
136+
}
137+
93138
type FormatTest struct {
94139
name string
95140
format string

0 commit comments

Comments
 (0)