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 binarydocumentation:
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