Skip to content

Commit

Permalink
integer encoding+decoding bounds checks elimination (#348)
Browse files Browse the repository at this point in the history
Speed up encoding and decoding of binary integers by providing bounds-check hints.
  • Loading branch information
mhr3 authored Jun 16, 2024
1 parent abadd67 commit c029b2c
Show file tree
Hide file tree
Showing 2 changed files with 182 additions and 17 deletions.
59 changes: 42 additions & 17 deletions msgp/integers.go
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
package msgp

import "encoding/binary"

/* ----------------------------------
integer encoding utilities
(inline-able)
Expand All @@ -11,6 +13,8 @@ package msgp
---------------------------------- */

func putMint64(b []byte, i int64) {
_ = b[8] // bounds check elimination

b[0] = mint64
b[1] = byte(i >> 56)
b[2] = byte(i >> 48)
Expand All @@ -23,13 +27,17 @@ func putMint64(b []byte, i int64) {
}

func getMint64(b []byte) int64 {
_ = b[8] // bounds check elimination

return (int64(b[1]) << 56) | (int64(b[2]) << 48) |
(int64(b[3]) << 40) | (int64(b[4]) << 32) |
(int64(b[5]) << 24) | (int64(b[6]) << 16) |
(int64(b[7]) << 8) | (int64(b[8]))
}

func putMint32(b []byte, i int32) {
_ = b[4] // bounds check elimination

b[0] = mint32
b[1] = byte(i >> 24)
b[2] = byte(i >> 16)
Expand All @@ -38,20 +46,28 @@ func putMint32(b []byte, i int32) {
}

func getMint32(b []byte) int32 {
_ = b[4] // bounds check elimination

return (int32(b[1]) << 24) | (int32(b[2]) << 16) | (int32(b[3]) << 8) | (int32(b[4]))
}

func putMint16(b []byte, i int16) {
_ = b[2] // bounds check elimination

b[0] = mint16
b[1] = byte(i >> 8)
b[2] = byte(i)
}

func getMint16(b []byte) (i int16) {
_ = b[2] // bounds check elimination

return (int16(b[1]) << 8) | int16(b[2])
}

func putMint8(b []byte, i int8) {
_ = b[1] // bounds check elimination

b[0] = mint8
b[1] = byte(i)
}
Expand All @@ -61,6 +77,8 @@ func getMint8(b []byte) (i int8) {
}

func putMuint64(b []byte, u uint64) {
_ = b[8] // bounds check elimination

b[0] = muint64
b[1] = byte(u >> 56)
b[2] = byte(u >> 48)
Expand All @@ -73,13 +91,17 @@ func putMuint64(b []byte, u uint64) {
}

func getMuint64(b []byte) uint64 {
_ = b[8] // bounds check elimination

return (uint64(b[1]) << 56) | (uint64(b[2]) << 48) |
(uint64(b[3]) << 40) | (uint64(b[4]) << 32) |
(uint64(b[5]) << 24) | (uint64(b[6]) << 16) |
(uint64(b[7]) << 8) | (uint64(b[8]))
}

func putMuint32(b []byte, u uint32) {
_ = b[4] // bounds check elimination

b[0] = muint32
b[1] = byte(u >> 24)
b[2] = byte(u >> 16)
Expand All @@ -88,20 +110,28 @@ func putMuint32(b []byte, u uint32) {
}

func getMuint32(b []byte) uint32 {
_ = b[4] // bounds check elimination

return (uint32(b[1]) << 24) | (uint32(b[2]) << 16) | (uint32(b[3]) << 8) | (uint32(b[4]))
}

func putMuint16(b []byte, u uint16) {
_ = b[2] // bounds check elimination

b[0] = muint16
b[1] = byte(u >> 8)
b[2] = byte(u)
}

func getMuint16(b []byte) uint16 {
_ = b[2] // bounds check elimination

return (uint16(b[1]) << 8) | uint16(b[2])
}

func putMuint8(b []byte, u uint8) {
_ = b[1] // bounds check elimination

b[0] = muint8
b[1] = byte(u)
}
Expand All @@ -111,28 +141,15 @@ func getMuint8(b []byte) uint8 {
}

func getUnix(b []byte) (sec int64, nsec int32) {
sec = (int64(b[0]) << 56) | (int64(b[1]) << 48) |
(int64(b[2]) << 40) | (int64(b[3]) << 32) |
(int64(b[4]) << 24) | (int64(b[5]) << 16) |
(int64(b[6]) << 8) | (int64(b[7]))
sec = int64(binary.BigEndian.Uint64(b))
nsec = int32(binary.BigEndian.Uint32(b[8:]))

nsec = (int32(b[8]) << 24) | (int32(b[9]) << 16) | (int32(b[10]) << 8) | (int32(b[11]))
return
}

func putUnix(b []byte, sec int64, nsec int32) {
b[0] = byte(sec >> 56)
b[1] = byte(sec >> 48)
b[2] = byte(sec >> 40)
b[3] = byte(sec >> 32)
b[4] = byte(sec >> 24)
b[5] = byte(sec >> 16)
b[6] = byte(sec >> 8)
b[7] = byte(sec)
b[8] = byte(nsec >> 24)
b[9] = byte(nsec >> 16)
b[10] = byte(nsec >> 8)
b[11] = byte(nsec)
binary.BigEndian.PutUint64(b, uint64(sec))
binary.BigEndian.PutUint32(b[8:], uint32(nsec))
}

/* -----------------------------
Expand All @@ -141,19 +158,25 @@ func putUnix(b []byte, sec int64, nsec int32) {

// write prefix and uint8
func prefixu8(b []byte, pre byte, sz uint8) {
_ = b[1] // bounds check elimination

b[0] = pre
b[1] = byte(sz)
}

// write prefix and big-endian uint16
func prefixu16(b []byte, pre byte, sz uint16) {
_ = b[2] // bounds check elimination

b[0] = pre
b[1] = byte(sz >> 8)
b[2] = byte(sz)
}

// write prefix and big-endian uint32
func prefixu32(b []byte, pre byte, sz uint32) {
_ = b[4] // bounds check elimination

b[0] = pre
b[1] = byte(sz >> 24)
b[2] = byte(sz >> 16)
Expand All @@ -162,6 +185,8 @@ func prefixu32(b []byte, pre byte, sz uint32) {
}

func prefixu64(b []byte, pre byte, sz uint64) {
_ = b[8] // bounds check elimination

b[0] = pre
b[1] = byte(sz >> 56)
b[2] = byte(sz >> 48)
Expand Down
140 changes: 140 additions & 0 deletions msgp/integers_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,140 @@
package msgp

import (
"encoding/binary"
"testing"
)

func BenchmarkIntegers(b *testing.B) {
bytes64 := make([]byte, 9)
bytes32 := make([]byte, 5)
bytes16 := make([]byte, 3)

b.Run("Int64", func(b *testing.B) {
b.Run("Put", func(b *testing.B) {
for i := 0; i < b.N; i++ {
putMint64(bytes64, -1234567890123456789)
}
})
b.Run("Get", func(b *testing.B) {
putMint64(bytes64, -1234567890123456789)
for i := 0; i < b.N; i++ {
getMint64(bytes64)
}
})
})
b.Run("Int32", func(b *testing.B) {
b.Run("Put", func(b *testing.B) {
for i := 0; i < b.N; i++ {
putMint32(bytes32, -123456789)
}
})
b.Run("Get", func(b *testing.B) {
putMint32(bytes32, -123456789)
for i := 0; i < b.N; i++ {
getMint32(bytes32)
}
})
})
b.Run("Int16", func(b *testing.B) {
b.Run("Put", func(b *testing.B) {
for i := 0; i < b.N; i++ {
putMint16(bytes16, -12345)
}
})
b.Run("Get", func(b *testing.B) {
putMint16(bytes16, -12345)
for i := 0; i < b.N; i++ {
getMint16(bytes16)
}
})
})

b.Run("Uint64", func(b *testing.B) {
b.Run("Put", func(b *testing.B) {
for i := 0; i < b.N; i++ {
putMuint64(bytes64, 1234567890123456789)
}
})
b.Run("Get", func(b *testing.B) {
putMuint64(bytes64, 1234567890123456789)
for i := 0; i < b.N; i++ {
getMuint64(bytes64)
}
})
})
b.Run("Uint32", func(b *testing.B) {
b.Run("Put", func(b *testing.B) {
for i := 0; i < b.N; i++ {
putMuint32(bytes32, 123456789)
}
})
b.Run("Get", func(b *testing.B) {
putMuint32(bytes32, 123456789)
for i := 0; i < b.N; i++ {
getMuint32(bytes32)
}
})
})
b.Run("Uint16", func(b *testing.B) {
b.Run("Put", func(b *testing.B) {
for i := 0; i < b.N; i++ {
putMuint16(bytes16, 12345)
}
})
b.Run("Get", func(b *testing.B) {
putMuint16(bytes16, 12345)
for i := 0; i < b.N; i++ {
getMuint16(bytes16)
}
})
})
}

func BenchmarkIntegersUnix(b *testing.B) {
bytes := make([]byte, 12)
var sec int64 = 1609459200
var nsec int32 = 123456789

b.Run("Get", func(b *testing.B) {
binary.BigEndian.PutUint64(bytes, uint64(sec))
binary.BigEndian.PutUint32(bytes[8:], uint32(nsec))
for i := 0; i < b.N; i++ {
getUnix(bytes)
}
})

b.Run("Put", func(b *testing.B) {
for i := 0; i < b.N; i++ {
putUnix(bytes, sec, nsec)
}
})
}

func BenchmarkIntegersPrefix(b *testing.B) {
bytesU16 := make([]byte, 3)
bytesU32 := make([]byte, 5)
bytesU64 := make([]byte, 9)

b.Run("u16", func(b *testing.B) {
var pre byte = 0x01
var sz uint16 = 12345
for i := 0; i < b.N; i++ {
prefixu16(bytesU16, pre, sz)
}
})
b.Run("u32", func(b *testing.B) {
var pre byte = 0x02
var sz uint32 = 123456789
for i := 0; i < b.N; i++ {
prefixu32(bytesU32, pre, sz)
}
})
b.Run("u64", func(b *testing.B) {
var pre byte = 0x03
var sz uint64 = 1234567890123456789
for i := 0; i < b.N; i++ {
prefixu64(bytesU64, pre, sz)
}
})
}

0 comments on commit c029b2c

Please sign in to comment.