This is two ways to get the same 4 bytes:
package main
import (
"encoding/binary"
"fmt"
)
func main() {
i := binary.LittleEndian.Uint32([]byte{1, 2, 3, 0})
bs := make([]byte, 4)
binary.LittleEndian.PutUint32(bs, uint32(i))
fmt.Println(bs[0] == 1 && bs[1] == 2 && bs[2] == 3 && bs[3] == 0)
bs = []byte{byte(i & 0x000000ff),
byte(i >> 8 & 0x000000ff),
byte(i >> 16 & 0x000000ff),
byte(i >> 24)}
fmt.Println(bs[0] == 1 && bs[1] == 2 && bs[2] == 3 && bs[3] == 0)
}
They both work. But which is the way the Go community would say is best?
Let's look inside the Go standard library:
func (littleEndian) PutUint32(b []byte, v uint32) {
_ = b[3] // early bounds check to guarantee safety of writes below
b[0] = byte(v)
b[1] = byte(v >> 8)
b[2] = byte(v >> 16)
b[3] = byte(v >> 24)
}
And see the Package binary
documentation:
This package favors simplicity over efficiency. Clients that require high-performance serialization, especially for large data structures, should look at more advanced solutions such as the encoding/gob package or protocol buffers.
Which way we should go, depends on a problem we are trying to solve:
When the efficiency is not the main concern (of the problem we are trying to solve with programming), The simplicity is the way to go (because less bug is the main goal here):
binary.LittleEndian.PutUint32
with an array (note you may use an array [4]byte{}
here, for efficiency and simplicity over make([]byte, 4)
): var v uint32 = 0x4030201
ary := [4]byte{}
binary.LittleEndian.PutUint32(ary[:], v)
fmt.Println(ary) // [1 2 3 4]
binary.LittleEndian.PutUint32
with a slice: var v uint32 = 0x4030201
b := make([]byte, 4)
binary.LittleEndian.PutUint32(b, v)
fmt.Println(b) // [1 2 3 4]
var v uint32 = 0x4030201
a := [4]byte{
byte(v),
byte(v >> 8),
byte(v >> 16),
byte(v >> 24),
}
fmt.Println(a) // [1 2 3 4]
unsafe.Pointer
(Just for efficiency or compatiblity or ...): var v uint32 = 1
a := (*[4]byte)(unsafe.Pointer(&v))
a[0] = 100 // same memory (Like `union` in the C language)
fmt.Println(a, v) // &[100 0 0 0] 100
unsafe.Pointer
and make a copy in one-line (so not a union
): var v uint32 = 0x4030201
a := *(*[4]byte)(unsafe.Pointer(&v))
fmt.Println(a) // [1 2 3 4]
unsafe.Pointer
with the copy
to a slice: b := make([]byte, 4)
copy(b, (*[4]byte)(unsafe.Pointer(&v))[:])
Try it all here
Benchmark:
BenchmarkFn1-8 580469606 1.94 ns/op
BenchmarkFn2-8 568699358 2.06 ns/op
BenchmarkFn3-8 604883466 1.86 ns/op
BenchmarkFn4-8 824232160 1.33 ns/op
BenchmarkFn5-8 626357875 1.82 ns/op
BenchmarkFn6-8 622969119 1.82 ns/op
BenchmarkFn7-8 469203398 2.35 ns/op
BenchmarkFn8-8 637403140 1.80 ns/op
BenchmarkFn9-8 647179550 1.80 ns/op
main.go
file:
package main
import (
"encoding/binary"
"unsafe"
)
// 1.94 ns/op
func fn1(v uint32) [4]byte {
ary := [4]byte{}
binary.LittleEndian.PutUint32(ary[:], v)
return ary
}
// 2.06 ns/op
func fn2(v uint32) []byte {
b := make([]byte, 4)
binary.LittleEndian.PutUint32(b, v)
return b
}
// 1.86 ns/op
func fn3(v uint32) [4]byte {
a := [4]byte{
byte(v),
byte(v >> 8),
byte(v >> 16),
byte(v >> 24),
}
return a
}
// 1.33 ns/op
func fn4(v uint32) *[4]byte {
a := (*[4]byte)(unsafe.Pointer(&v))
return a
}
// 1.82 ns/op
func fn5(v uint32) [4]byte {
a := *(*[4]byte)(unsafe.Pointer(&v))
return a
}
// 1.82 ns/op
func fn6(v uint32) []byte {
b := make([]byte, 4)
copy(b, (*[4]byte)(unsafe.Pointer(&v))[:])
return b
}
// 2.35 ns/op
func fn7(v uint32) [4]byte {
b := [4]byte{}
copy(b[:], (*[4]byte)(unsafe.Pointer(&v))[:])
return b
}
// 1.80 ns/op
func fn8(v uint32) *[4]byte {
b := [4]byte{}
copy(b[:], (*[4]byte)(unsafe.Pointer(&v))[:])
return &b
}
//1.80 ns/op
func fn9(v uint32) []byte {
b := [4]byte{}
copy(b[:], (*[4]byte)(unsafe.Pointer(&v))[:])
return b[:]
}
func main() {}
main_test.go
file:
package main
import "testing"
var result int
func BenchmarkFn1(b *testing.B) {
sum := 0
for i := 0; i < b.N; i++ {
r := fn1(uint32(i))
sum += int(r[0]) + int(r[1]) + int(r[2]) + int(r[3])
}
result = sum
}
func BenchmarkFn2(b *testing.B) {
sum := 0
for i := 0; i < b.N; i++ {
r := fn2(uint32(i))
sum += int(r[0]) + int(r[1]) + int(r[2]) + int(r[3])
}
result = sum
}
func BenchmarkFn3(b *testing.B) {
sum := 0
for i := 0; i < b.N; i++ {
r := fn3(uint32(i))
sum += int(r[0]) + int(r[1]) + int(r[2]) + int(r[3])
}
result = sum
}
func BenchmarkFn4(b *testing.B) {
sum := 0
for i := 0; i < b.N; i++ {
r := fn4(uint32(i))
sum += int(r[0]) + int(r[1]) + int(r[2]) + int(r[3])
}
result = sum
}
func BenchmarkFn5(b *testing.B) {
sum := 0
for i := 0; i < b.N; i++ {
r := fn5(uint32(i))
sum += int(r[0]) + int(r[1]) + int(r[2]) + int(r[3])
}
result = sum
}
func BenchmarkFn6(b *testing.B) {
sum := 0
for i := 0; i < b.N; i++ {
r := fn6(uint32(i))
sum += int(r[0]) + int(r[1]) + int(r[2]) + int(r[3])
}
result = sum
}
func BenchmarkFn7(b *testing.B) {
sum := 0
for i := 0; i < b.N; i++ {
r := fn7(uint32(i))
sum += int(r[0]) + int(r[1]) + int(r[2]) + int(r[3])
}
result = sum
}
func BenchmarkFn8(b *testing.B) {
sum := 0
for i := 0; i < b.N; i++ {
r := fn8(uint32(i))
sum += int(r[0]) + int(r[1]) + int(r[2]) + int(r[3])
}
result = sum
}
func BenchmarkFn9(b *testing.B) {
sum := 0
for i := 0; i < b.N; i++ {
r := fn9(uint32(i))
sum += int(r[0]) + int(r[1]) + int(r[2]) + int(r[3])
}
result = sum
}
Ultra fast and unsafe (shared memory e.g. C union
and it may differ from little-endian to big-endian system):
a := (*[4]byte)(unsafe.Pointer(&v))
Fast and safe (copy of v as an array):
a := [4]byte{byte(v), byte(v >> 8), byte(v >> 16), byte(v >> 24)}
Simple and fast (copy of v as an array using the standard library):
ary := [4]byte{}
binary.LittleEndian.PutUint32(ary[:], v)
Beautiful (copy of v as a slice using the standard library):
b := make([]byte, 4)
binary.LittleEndian.PutUint32(b, v)
User contributions licensed under CC BY-SA 3.0