Go 的方式是 PutUint32 还是直接使用 >> 运算符?

And*_*row 1 binary go

有两种方法可以获得相同的 4 个字节:

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)
}
Run Code Online (Sandbox Code Playgroud)

他们都工作。但 Go 社区认为哪种方式最好呢?

was*_*mup 5

让我们看看 Go 标准库的内部:

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)
}
Run Code Online (Sandbox Code Playgroud)

并查看Package binary文档:

该软件包更注重简单性而不是效率。需要高性能序列化的客户端,特别是对于大型数据结构,应该考虑更高级的解决方案,例如编码/gob 包或协议缓冲区。

我们应该走哪条路,取决于我们试图解决的问题:
效率不是主要关注点(我们试图通过编程解决的问题)时,简单就是要走的路(因为更少的错误是最重要的)主要目标在这里):

  1. 您可以将 Go 标准库函数与数组一起使用(请注意,为了提高效率简单性binary.LittleEndian.PutUint32您可以在此处使用数组): [4]byte{}make([]byte, 4)
        var v uint32 = 0x4030201
        ary := [4]byte{}
        binary.LittleEndian.PutUint32(ary[:], v)
        fmt.Println(ary) // [1 2 3 4]
Run Code Online (Sandbox Code Playgroud)
  1. 您可以将 Go 标准库函数binary.LittleEndian.PutUint32与切片一起使用:
        var v uint32 = 0x4030201
        b := make([]byte, 4)
        binary.LittleEndian.PutUint32(b, v)
        fmt.Println(b) // [1 2 3 4]
Run Code Online (Sandbox Code Playgroud)
  1. 你可以直接写(效率高,因为没有库函数调用):
        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]
Run Code Online (Sandbox Code Playgroud)
  1. 您可以使用unsafe.Pointer(只是为了效率或兼容性或......):
        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
Run Code Online (Sandbox Code Playgroud)
  1. unsafe.Pointer您可以在一行中使用并制作一份副本(因此不是union):
        var v uint32 = 0x4030201
        a := *(*[4]byte)(unsafe.Pointer(&v))
        fmt.Println(a) // [1 2 3 4]
Run Code Online (Sandbox Code Playgroud)
  1. 您可以unsafe.Pointercopy切片一起使用:
    b := make([]byte, 4)
    copy(b, (*[4]byte)(unsafe.Pointer(&v))[:])
Run Code Online (Sandbox Code Playgroud)

在这里尝试一切


基准:

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
Run Code Online (Sandbox Code Playgroud)

main.go文件:

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() {}

Run Code Online (Sandbox Code Playgroud)

main_test.go文件:

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
}

Run Code Online (Sandbox Code Playgroud)

结论

超快且不安全(例如共享内存,C union从小端到大端系统可能有所不同):

a := (*[4]byte)(unsafe.Pointer(&v))
Run Code Online (Sandbox Code Playgroud)

快速且安全(v 作为数组的副本):

a := [4]byte{byte(v), byte(v >> 8), byte(v >> 16), byte(v >> 24)}
Run Code Online (Sandbox Code Playgroud)

简单快速(使用标准库将 v 复制为数组):

    ary := [4]byte{}
    binary.LittleEndian.PutUint32(ary[:], v)
Run Code Online (Sandbox Code Playgroud)

漂亮(使用标准库将 v 复制为切片):

    b := make([]byte, 4)
    binary.LittleEndian.PutUint32(b, v)
Run Code Online (Sandbox Code Playgroud)