1
0
mirror of https://github.com/golang/go synced 2024-11-26 04:17:59 -07:00

time: implement the encoding.(Binary|Text)Appender for Time

"Time.Marshal(Binary|Text)" could also gain some performance
improvements. Here is the benchmark highlight:

                │     old      │                 new                 │
                │    sec/op    │   sec/op     vs base                │
MarshalText-8     104.00n ± 3%   67.27n ± 2%  -35.32% (p=0.000 n=10)
MarshalBinary-8    31.77n ± 2%   12.13n ± 1%  -61.82% (p=0.000 n=10)
geomean            57.48n        28.57n       -50.30%

                │    old     │                  new                   │
                │    B/op    │   B/op     vs base                     │
MarshalText-8     48.00 ± 0%   0.00 ± 0%  -100.00% (p=0.000 n=10)
MarshalBinary-8   16.00 ± 0%   0.00 ± 0%  -100.00% (p=0.000 n=10)

                │    old     │                   new                   │
                │ allocs/op  │ allocs/op   vs base                     │
MarshalText-8     1.000 ± 0%   0.000 ± 0%  -100.00% (p=0.000 n=10)
MarshalBinary-8   1.000 ± 0%   0.000 ± 0%  -100.00% (p=0.000 n=10)

For #62384
This commit is contained in:
apocelipes 2024-08-19 19:30:06 +08:00
parent 27093581b2
commit e04f8df9c2
4 changed files with 68 additions and 28 deletions

View File

@ -9,3 +9,5 @@ pkg math/big, method (*Float) AppendText([]uint8) ([]uint8, error) #62384
pkg math/big, method (*Int) AppendText([]uint8) ([]uint8, error) #62384 pkg math/big, method (*Int) AppendText([]uint8) ([]uint8, error) #62384
pkg math/big, method (*Rat) AppendText([]uint8) ([]uint8, error) #62384 pkg math/big, method (*Rat) AppendText([]uint8) ([]uint8, error) #62384
pkg regexp, method (*Regexp) AppendText([]uint8) ([]uint8, error) #62384 pkg regexp, method (*Regexp) AppendText([]uint8) ([]uint8, error) #62384
pkg time, method (Time) AppendBinary([]uint8) ([]uint8, error) #62384
pkg time, method (Time) AppendText([]uint8) ([]uint8, error) #62384

View File

@ -0,0 +1 @@
[Time] now implements the [encoding.BinaryAppender] and [encoding.TextAppender] interfaces.

View File

@ -119,9 +119,9 @@ import (
// these methods does not change the actual instant it represents, only the time // these methods does not change the actual instant it represents, only the time
// zone in which to interpret it. // zone in which to interpret it.
// //
// Representations of a Time value saved by the [Time.GobEncode], [Time.MarshalBinary], // Representations of a Time value saved by the [Time.GobEncode], [Time.MarshalBinary], [Time.AppendBinary],
// [Time.MarshalJSON], and [Time.MarshalText] methods store the [Time.Location]'s offset, but not // [Time.MarshalJSON], [Time.MarshalText] and [Time.AppendText] methods store the [Time.Location]'s offset,
// the location name. They therefore lose information about Daylight Saving Time. // but not the location name. They therefore lose information about Daylight Saving Time.
// //
// In addition to the required “wall clock” reading, a Time may contain an optional // In addition to the required “wall clock” reading, a Time may contain an optional
// reading of the current process's monotonic clock, to provide additional precision // reading of the current process's monotonic clock, to provide additional precision
@ -1435,8 +1435,8 @@ const (
timeBinaryVersionV2 // For LMT only timeBinaryVersionV2 // For LMT only
) )
// MarshalBinary implements the encoding.BinaryMarshaler interface. // AppendBinary implements the [encoding.BinaryAppender] interface.
func (t Time) MarshalBinary() ([]byte, error) { func (t Time) AppendBinary(b []byte) ([]byte, error) {
var offsetMin int16 // minutes east of UTC. -1 is UTC. var offsetMin int16 // minutes east of UTC. -1 is UTC.
var offsetSec int8 var offsetSec int8
version := timeBinaryVersionV1 version := timeBinaryVersionV1
@ -1452,38 +1452,46 @@ func (t Time) MarshalBinary() ([]byte, error) {
offset /= 60 offset /= 60
if offset < -32768 || offset == -1 || offset > 32767 { if offset < -32768 || offset == -1 || offset > 32767 {
return nil, errors.New("Time.MarshalBinary: unexpected zone offset") return b, errors.New("Time.MarshalBinary: unexpected zone offset")
} }
offsetMin = int16(offset) offsetMin = int16(offset)
} }
sec := t.sec() sec := t.sec()
nsec := t.nsec() nsec := t.nsec()
enc := []byte{ b = append(b,
version, // byte 0 : version version, // byte 0 : version
byte(sec >> 56), // bytes 1-8: seconds byte(sec>>56), // bytes 1-8: seconds
byte(sec >> 48), byte(sec>>48),
byte(sec >> 40), byte(sec>>40),
byte(sec >> 32), byte(sec>>32),
byte(sec >> 24), byte(sec>>24),
byte(sec >> 16), byte(sec>>16),
byte(sec >> 8), byte(sec>>8),
byte(sec), byte(sec),
byte(nsec >> 24), // bytes 9-12: nanoseconds byte(nsec>>24), // bytes 9-12: nanoseconds
byte(nsec >> 16), byte(nsec>>16),
byte(nsec >> 8), byte(nsec>>8),
byte(nsec), byte(nsec),
byte(offsetMin >> 8), // bytes 13-14: zone offset in minutes byte(offsetMin>>8), // bytes 13-14: zone offset in minutes
byte(offsetMin), byte(offsetMin),
} )
if version == timeBinaryVersionV2 { if version == timeBinaryVersionV2 {
enc = append(enc, byte(offsetSec)) b = append(b, byte(offsetSec))
} }
return b, nil
return enc, nil
} }
// UnmarshalBinary implements the encoding.BinaryUnmarshaler interface. // MarshalBinary implements the [encoding.BinaryMarshaler] interface.
func (t Time) MarshalBinary() ([]byte, error) {
b, err := t.AppendBinary(make([]byte, 0, 16))
if err != nil {
return nil, err
}
return b, nil
}
// UnmarshalBinary implements the [encoding.BinaryUnmarshaler] interface.
func (t *Time) UnmarshalBinary(data []byte) error { func (t *Time) UnmarshalBinary(data []byte) error {
buf := data buf := data
if len(buf) == 0 { if len(buf) == 0 {
@ -1576,12 +1584,11 @@ func (t *Time) UnmarshalJSON(data []byte) error {
return err return err
} }
// MarshalText implements the [encoding.TextMarshaler] interface. // AppendText implements the [encoding.TextAppender] interface.
// The time is formatted in RFC 3339 format with sub-second precision. // The time is formatted in RFC 3339 format with sub-second precision.
// If the timestamp cannot be represented as valid RFC 3339 // If the timestamp cannot be represented as valid RFC 3339
// (e.g., the year is out of range), then an error is reported. // (e.g., the year is out of range), then an error is returned.
func (t Time) MarshalText() ([]byte, error) { func (t Time) AppendText(b []byte) ([]byte, error) {
b := make([]byte, 0, len(RFC3339Nano))
b, err := t.appendStrictRFC3339(b) b, err := t.appendStrictRFC3339(b)
if err != nil { if err != nil {
return nil, errors.New("Time.MarshalText: " + err.Error()) return nil, errors.New("Time.MarshalText: " + err.Error())
@ -1589,6 +1596,14 @@ func (t Time) MarshalText() ([]byte, error) {
return b, nil return b, nil
} }
// MarshalText implements the [encoding.TextMarshaler] interface. The output
// matches that of calling the [Time.AppendText] method.
//
// See [Time.AppendText] for more information.
func (t Time) MarshalText() ([]byte, error) {
return t.AppendText(make([]byte, 0, len(RFC3339Nano)))
}
// UnmarshalText implements the [encoding.TextUnmarshaler] interface. // UnmarshalText implements the [encoding.TextUnmarshaler] interface.
// The time must be in the RFC 3339 format. // The time must be in the RFC 3339 format.
func (t *Time) UnmarshalText(data []byte) error { func (t *Time) UnmarshalText(data []byte) error {

View File

@ -1403,6 +1403,13 @@ var defaultLocTests = []struct {
{"UnixMilli", func(t1, t2 Time) bool { return t1.UnixMilli() == t2.UnixMilli() }}, {"UnixMilli", func(t1, t2 Time) bool { return t1.UnixMilli() == t2.UnixMilli() }},
{"UnixMicro", func(t1, t2 Time) bool { return t1.UnixMicro() == t2.UnixMicro() }}, {"UnixMicro", func(t1, t2 Time) bool { return t1.UnixMicro() == t2.UnixMicro() }},
{"AppendBinary", func(t1, t2 Time) bool {
buf1 := make([]byte, 4, 32)
buf2 := make([]byte, 4, 32)
a1, b1 := t1.AppendBinary(buf1)
a2, b2 := t2.AppendBinary(buf2)
return bytes.Equal(a1[4:], a2[4:]) && b1 == b2
}},
{"MarshalBinary", func(t1, t2 Time) bool { {"MarshalBinary", func(t1, t2 Time) bool {
a1, b1 := t1.MarshalBinary() a1, b1 := t1.MarshalBinary()
a2, b2 := t2.MarshalBinary() a2, b2 := t2.MarshalBinary()
@ -1418,6 +1425,14 @@ var defaultLocTests = []struct {
a2, b2 := t2.MarshalJSON() a2, b2 := t2.MarshalJSON()
return bytes.Equal(a1, a2) && b1 == b2 return bytes.Equal(a1, a2) && b1 == b2
}}, }},
{"AppendText", func(t1, t2 Time) bool {
maxCap := len(RFC3339Nano) + 4
buf1 := make([]byte, 4, maxCap)
buf2 := make([]byte, 4, maxCap)
a1, b1 := t1.AppendText(buf1)
a2, b2 := t2.AppendText(buf2)
return bytes.Equal(a1[4:], a2[4:]) && b1 == b2
}},
{"MarshalText", func(t1, t2 Time) bool { {"MarshalText", func(t1, t2 Time) bool {
a1, b1 := t1.MarshalText() a1, b1 := t1.MarshalText()
a2, b2 := t2.MarshalText() a2, b2 := t2.MarshalText()
@ -1510,6 +1525,13 @@ func BenchmarkMarshalText(b *testing.B) {
} }
} }
func BenchmarkMarshalBinary(b *testing.B) {
t := Now()
for i := 0; i < b.N; i++ {
t.MarshalBinary()
}
}
func BenchmarkParse(b *testing.B) { func BenchmarkParse(b *testing.B) {
for i := 0; i < b.N; i++ { for i := 0; i < b.N; i++ {
Parse(ANSIC, "Mon Jan 2 15:04:05 2006") Parse(ANSIC, "Mon Jan 2 15:04:05 2006")