From e2eb6af3e3ebecff8647c01f81eac92bf864fc31 Mon Sep 17 00:00:00 2001 From: Brian Atkinson Date: Sun, 15 Jul 2018 16:38:55 -0700 Subject: [PATCH 1/2] [Go] Unroll WriteUint64 and WriteInt64. This enables both WriteUint64 and WriteInt64 to both be inlined as well as implemented with a single assembly instruction. The current Go compiler refuses to inline functions with for loops. The compiler is also not smart enough to produce a single assembly instruction for the for-loop. --- go/encode.go | 22 ++++++++++++++++------ 1 file changed, 16 insertions(+), 6 deletions(-) diff --git a/go/encode.go b/go/encode.go index 48ff36ef0..6e47c056c 100644 --- a/go/encode.go +++ b/go/encode.go @@ -159,9 +159,14 @@ func WriteUint32(buf []byte, n uint32) { // WriteUint64 encodes a little-endian uint64 into a byte slice. func WriteUint64(buf []byte, n uint64) { - for i := uint(0); i < uint(SizeUint64); i++ { - buf[i] = byte(n >> (i * 8)) - } + buf[0] = byte(n) + buf[1] = byte(n >> 8) + buf[2] = byte(n >> 16) + buf[3] = byte(n >> 24) + buf[4] = byte(n >> 32) + buf[5] = byte(n >> 40) + buf[6] = byte(n >> 48) + buf[7] = byte(n >> 56) } // WriteInt8 encodes a little-endian int8 into a byte slice. @@ -185,9 +190,14 @@ func WriteInt32(buf []byte, n int32) { // WriteInt64 encodes a little-endian int64 into a byte slice. func WriteInt64(buf []byte, n int64) { - for i := uint(0); i < uint(SizeInt64); i++ { - buf[i] = byte(n >> (i * 8)) - } + buf[0] = byte(n) + buf[1] = byte(n >> 8) + buf[2] = byte(n >> 16) + buf[3] = byte(n >> 24) + buf[4] = byte(n >> 32) + buf[5] = byte(n >> 40) + buf[6] = byte(n >> 48) + buf[7] = byte(n >> 56) } // WriteFloat32 encodes a little-endian float32 into a byte slice. From b3e4d9169b3e6ec86bdd9d6f5aff9ee7f87e119a Mon Sep 17 00:00:00 2001 From: Brian Atkinson Date: Sun, 15 Jul 2018 16:38:56 -0700 Subject: [PATCH 2/2] [Go] Force a single, early bounds check on read and write paths. As an example, GetInt64 used to perform 8 bounds checks, one for each slice access. By performing a bound check on the highest index, the number of checks is reduced to one through bounds-check-elimination. --- go/encode.go | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/go/encode.go b/go/encode.go index 6e47c056c..72d4f3a15 100644 --- a/go/encode.go +++ b/go/encode.go @@ -36,6 +36,7 @@ func GetUint8(buf []byte) (n uint8) { // GetUint16 decodes a little-endian uint16 from a byte slice. func GetUint16(buf []byte) (n uint16) { + _ = buf[1] // Force one bounds check. See: golang.org/issue/14808 n |= uint16(buf[0]) n |= uint16(buf[1]) << 8 return @@ -43,6 +44,7 @@ func GetUint16(buf []byte) (n uint16) { // GetUint32 decodes a little-endian uint32 from a byte slice. func GetUint32(buf []byte) (n uint32) { + _ = buf[3] // Force one bounds check. See: golang.org/issue/14808 n |= uint32(buf[0]) n |= uint32(buf[1]) << 8 n |= uint32(buf[2]) << 16 @@ -52,6 +54,7 @@ func GetUint32(buf []byte) (n uint32) { // GetUint64 decodes a little-endian uint64 from a byte slice. func GetUint64(buf []byte) (n uint64) { + _ = buf[7] // Force one bounds check. See: golang.org/issue/14808 n |= uint64(buf[0]) n |= uint64(buf[1]) << 8 n |= uint64(buf[2]) << 16 @@ -71,6 +74,7 @@ func GetInt8(buf []byte) (n int8) { // GetInt16 decodes a little-endian int16 from a byte slice. func GetInt16(buf []byte) (n int16) { + _ = buf[1] // Force one bounds check. See: golang.org/issue/14808 n |= int16(buf[0]) n |= int16(buf[1]) << 8 return @@ -78,6 +82,7 @@ func GetInt16(buf []byte) (n int16) { // GetInt32 decodes a little-endian int32 from a byte slice. func GetInt32(buf []byte) (n int32) { + _ = buf[3] // Force one bounds check. See: golang.org/issue/14808 n |= int32(buf[0]) n |= int32(buf[1]) << 8 n |= int32(buf[2]) << 16 @@ -87,6 +92,7 @@ func GetInt32(buf []byte) (n int32) { // GetInt64 decodes a little-endian int64 from a byte slice. func GetInt64(buf []byte) (n int64) { + _ = buf[7] // Force one bounds check. See: golang.org/issue/14808 n |= int64(buf[0]) n |= int64(buf[1]) << 8 n |= int64(buf[2]) << 16 @@ -145,12 +151,14 @@ func WriteUint8(buf []byte, n uint8) { // WriteUint16 encodes a little-endian uint16 into a byte slice. func WriteUint16(buf []byte, n uint16) { + _ = buf[1] // Force one bounds check. See: golang.org/issue/14808 buf[0] = byte(n) buf[1] = byte(n >> 8) } // WriteUint32 encodes a little-endian uint32 into a byte slice. func WriteUint32(buf []byte, n uint32) { + _ = buf[3] // Force one bounds check. See: golang.org/issue/14808 buf[0] = byte(n) buf[1] = byte(n >> 8) buf[2] = byte(n >> 16) @@ -159,6 +167,7 @@ func WriteUint32(buf []byte, n uint32) { // WriteUint64 encodes a little-endian uint64 into a byte slice. func WriteUint64(buf []byte, n uint64) { + _ = buf[7] // Force one bounds check. See: golang.org/issue/14808 buf[0] = byte(n) buf[1] = byte(n >> 8) buf[2] = byte(n >> 16) @@ -176,12 +185,14 @@ func WriteInt8(buf []byte, n int8) { // WriteInt16 encodes a little-endian int16 into a byte slice. func WriteInt16(buf []byte, n int16) { + _ = buf[1] // Force one bounds check. See: golang.org/issue/14808 buf[0] = byte(n) buf[1] = byte(n >> 8) } // WriteInt32 encodes a little-endian int32 into a byte slice. func WriteInt32(buf []byte, n int32) { + _ = buf[3] // Force one bounds check. See: golang.org/issue/14808 buf[0] = byte(n) buf[1] = byte(n >> 8) buf[2] = byte(n >> 16) @@ -190,6 +201,7 @@ func WriteInt32(buf []byte, n int32) { // WriteInt64 encodes a little-endian int64 into a byte slice. func WriteInt64(buf []byte, n int64) { + _ = buf[7] // Force one bounds check. See: golang.org/issue/14808 buf[0] = byte(n) buf[1] = byte(n >> 8) buf[2] = byte(n >> 16)