Skip to content

Commit 384f060

Browse files
committed
time: add a time.Read method which does not allocate
The following benchmark marshals a struct with 100 time.Time objects. Old uses MarshalBinary where new uses Read. ``` goos: darwin goarch: arm64 pkg: utils/nats cpu: Apple M2 │ old.txt │ new5.txt │ │ sec/op │ sec/op vs base │ Encoders/mus/raw-8 15.259µ ± 91% 6.268µ ± 0% -58.92% (p=0.000 n=10) │ old.txt │ new5.txt │ │ bytes │ bytes vs base │ Encoders/mus/raw-8 4.001Ki ± 0% 4.001Ki ± 0% ~ (p=1.000 n=10) ¹ ¹ all samples are equal │ old.txt │ new5.txt │ │ B/op │ B/op vs base │ Encoders/mus/raw-8 1.562Ki ± 0% 0.000Ki ± 0% -100.00% (p=0.000 n=10) │ old.txt │ new5.txt │ │ allocs/op │ allocs/op vs base │ Encoders/mus/raw-8 100.0 ± 0% 0.0 ± 0% -100.00% (p=0.000 n=10) ``` Signed-off-by: Sylvain Rabot <[email protected]>
1 parent 9f9dd2b commit 384f060

File tree

1 file changed

+53
-22
lines changed

1 file changed

+53
-22
lines changed

src/time/time.go

+53-22
Original file line numberDiff line numberDiff line change
@@ -1248,8 +1248,8 @@ const (
12481248
timeBinaryVersionV2 // For LMT only
12491249
)
12501250

1251-
// MarshalBinary implements the encoding.BinaryMarshaler interface.
1252-
func (t Time) MarshalBinary() ([]byte, error) {
1251+
// read reads the binary representation of a Time value from the b byte slice.
1252+
func (t Time) read(b []byte) (int, error) {
12531253
var offsetMin int16 // minutes east of UTC. -1 is UTC.
12541254
var offsetSec int8
12551255
version := timeBinaryVersionV1
@@ -1265,35 +1265,66 @@ func (t Time) MarshalBinary() ([]byte, error) {
12651265

12661266
offset /= 60
12671267
if offset < -32768 || offset == -1 || offset > 32767 {
1268-
return nil, errors.New("Time.MarshalBinary: unexpected zone offset")
1268+
return 0, errors.New("unexpected zone offset")
12691269
}
12701270
offsetMin = int16(offset)
12711271
}
12721272

1273+
if version == timeBinaryVersionV1 && len(b) < 15 {
1274+
return 0, errors.New("insufficient buffer size")
1275+
} else if version == timeBinaryVersionV2 && len(b) < 16 {
1276+
return 0, errors.New("insufficient buffer size")
1277+
}
1278+
12731279
sec := t.sec()
12741280
nsec := t.nsec()
1275-
enc := []byte{
1276-
version, // byte 0 : version
1277-
byte(sec >> 56), // bytes 1-8: seconds
1278-
byte(sec >> 48),
1279-
byte(sec >> 40),
1280-
byte(sec >> 32),
1281-
byte(sec >> 24),
1282-
byte(sec >> 16),
1283-
byte(sec >> 8),
1284-
byte(sec),
1285-
byte(nsec >> 24), // bytes 9-12: nanoseconds
1286-
byte(nsec >> 16),
1287-
byte(nsec >> 8),
1288-
byte(nsec),
1289-
byte(offsetMin >> 8), // bytes 13-14: zone offset in minutes
1290-
byte(offsetMin),
1291-
}
1281+
_ = b[14]
1282+
b[0] = version // byte 0 : version
1283+
b[1] = byte(sec >> 56) // bytes 1-8: seconds
1284+
b[2] = byte(sec >> 48)
1285+
b[3] = byte(sec >> 40)
1286+
b[4] = byte(sec >> 32)
1287+
b[5] = byte(sec >> 24)
1288+
b[6] = byte(sec >> 16)
1289+
b[7] = byte(sec >> 8)
1290+
b[8] = byte(sec)
1291+
b[9] = byte(nsec >> 24) // bytes 9-12: nanoseconds
1292+
b[10] = byte(nsec >> 16)
1293+
b[11] = byte(nsec >> 8)
1294+
b[12] = byte(nsec)
1295+
b[13] = byte(offsetMin >> 8) // bytes 13-14: zone offset in minutes
1296+
b[14] = byte(offsetMin)
1297+
12921298
if version == timeBinaryVersionV2 {
1293-
enc = append(enc, byte(offsetSec))
1299+
b[15] = byte(offsetSec)
1300+
return 16, nil
1301+
}
1302+
1303+
return 15, nil
1304+
}
1305+
1306+
// Read implements the io.Reader interface.
1307+
// It reads the binary representation of a Time value into the p byte slice.
1308+
// In most cases it needs p to have a length of at least 15 bytes.
1309+
// However, if the time value is in the Local location and the zone offset
1310+
// minutes modulo 60 is not zero, it needs the p to have a length of at least 16
1311+
// bytes.
1312+
func (t Time) Read(p []byte) (int, error) {
1313+
if l, err := t.read(p); err != nil {
1314+
return l, errors.New("Time.Read: " + err.Error())
1315+
} else {
1316+
return l, nil
12941317
}
1318+
}
12951319

1296-
return enc, nil
1320+
// MarshalBinary implements the encoding.BinaryMarshaler interface.
1321+
func (t Time) MarshalBinary() ([]byte, error) {
1322+
p := make([]byte, 16)
1323+
if l, err := t.read(p); err != nil {
1324+
return nil, errors.New("Time.MarshalBinary: " + err.Error())
1325+
} else {
1326+
return p[:l], nil
1327+
}
12971328
}
12981329

12991330
// UnmarshalBinary implements the encoding.BinaryUnmarshaler interface.

0 commit comments

Comments
 (0)