一尘不染

Concat字节数组

go

有人可以指出以下更有效的版本吗

    b:=make([]byte,0,sizeTotal)
    b=append(b,size...)
    b=append(b,contentType...)
    b=append(b,lenCallbackid...)
    b=append(b,lenTarget...)
    b=append(b,lenAction...)
    b=append(b,lenContent...)
    b=append(b,callbackid...)
    b=append(b,target...)
    b=append(b,action...)
    b=append(b,content...)

每个变量都是一个字节片,大小不一 sizeTotal

Update

码:

type Message struct {
    size        uint32
    contentType uint8
    callbackId  string
    target      string
    action      string
    content     string
}


var res []byte
var b []byte = make([]byte,0,4096)

func (m *Message)ToByte()[]byte{
    callbackIdIntLen:=len(m.callbackId)
    targetIntLen := len(m.target)
    actionIntLen := len(m.action)
    contentIntLen := len(m.content)
    lenCallbackid:=make([]byte,4)
    binary.LittleEndian.PutUint32(lenCallbackid, uint32(callbackIdIntLen))
    callbackid := []byte(m.callbackId)
    lenTarget := make([]byte,4)
    binary.LittleEndian.PutUint32(lenTarget, uint32(targetIntLen))
    target:=[]byte(m.target)
    lenAction := make([]byte,4)
    binary.LittleEndian.PutUint32(lenAction, uint32(actionIntLen))
    action := []byte(m.action)
    lenContent:= make([]byte,4)
    binary.LittleEndian.PutUint32(lenContent, uint32(contentIntLen))
    content := []byte(m.content)
    sizeTotal:= 21+callbackIdIntLen+targetIntLen+actionIntLen+contentIntLen
    size := make([]byte,4)
    binary.LittleEndian.PutUint32(size, uint32(sizeTotal))
    b=b[:0]
    b=append(b,size...)
    b=append(b,byte(m.contentType))
    b=append(b,lenCallbackid...)
    b=append(b,lenTarget...)
    b=append(b,lenAction...)
    b=append(b,lenContent...)
    b=append(b,callbackid...)
    b=append(b,target...)
    b=append(b,action...)
    b=append(b,content...)
    res = b
    return b
}

func FromByte(bytes []byte)(*Message){
    size         :=binary.LittleEndian.Uint32(bytes[0:4])
    contentType  :=bytes[4:5][0]
    lenCallbackid:=binary.LittleEndian.Uint32(bytes[5:9])
    lenTarget    :=binary.LittleEndian.Uint32(bytes[9:13])
    lenAction    :=binary.LittleEndian.Uint32(bytes[13:17])
    lenContent   :=binary.LittleEndian.Uint32(bytes[17:21])
    callbackid   := string(bytes[21:21+lenCallbackid])
    target:= string(bytes[21+lenCallbackid:21+lenCallbackid+lenTarget])
    action:= string(bytes[21+lenCallbackid+lenTarget:21+lenCallbackid+lenTarget+lenAction])
    content:=string(bytes[size-lenContent:size])
    return &Message{size,contentType,callbackid,target,action,content}
}

Benchs

func BenchmarkMessageToByte(b *testing.B) {
    m:=NewMessage(uint8(3),"agsdggsdasagdsdgsgddggds","sometarSFAFFget","somFSAFSAFFSeaction","somfasfsasfafsejsonzhit")
    for n := 0; n < b.N; n++ {
        m.ToByte()
    }
}


func BenchmarkMessageFromByte(b *testing.B) {
    m:=NewMessage(uint8(1),"sagdsgaasdg","soSASFASFASAFSFASFAGmetarget","adsgdgsagdssgdsgd","agsdsdgsagdsdgasdg").ToByte()
    for n := 0; n < b.N; n++ {
        FromByte(m)
    }
}


func BenchmarkStringToByte(b *testing.B) {
    for n := 0; n < b.N; n++ {
        _ = []byte("abcdefghijklmnoqrstuvwxyz")
    }
}

func BenchmarkStringFromByte(b *testing.B) {
    s:=[]byte("abcdefghijklmnoqrstuvwxyz")
    for n := 0; n < b.N; n++ {
        _ = string(s)
    }
}


func BenchmarkUintToByte(b *testing.B) {
    for n := 0; n < b.N; n++ {
        i:=make([]byte,4)
        binary.LittleEndian.PutUint32(i, uint32(99))
    }
}

func BenchmarkUintFromByte(b *testing.B) {
    i:=make([]byte,4)
    binary.LittleEndian.PutUint32(i, uint32(99))
    for n := 0; n < b.N; n++ {
        binary.LittleEndian.Uint32(i)
    }
}

基准测试结果:

   BenchmarkMessageToByte     10000000               280 ns/op
   BenchmarkMessageFromByte   10000000               293 ns/op
   BenchmarkStringToByte      50000000               55.1 ns/op
   BenchmarkStringFromByte    50000000               49.7 ns/op
   BenchmarkUintToByte        1000000000             2.14 ns/op
   BenchmarkUintFromByte      2000000000             1.71 ns/op

阅读 314

收藏
2020-07-02

共1个答案

一尘不染

如果已经分配了内存,则x = append(x,a …)的序列在Go中非常有效。

在您的示例中,初始分配(制造)的成本可能比附加序列的成本高。这取决于字段的大小。考虑以下基准:

package main

import (
    "testing"
)

const sizeTotal = 25

var res []byte // To enforce heap allocation

func BenchmarkWithAlloc(b *testing.B) {

    a := []byte("abcde")

    for i := 0; i < b.N; i++ {
        x := make([]byte, 0, sizeTotal)
        x = append(x, a...)
        x = append(x, a...)
        x = append(x, a...)
        x = append(x, a...)
        x = append(x, a...)
        res = x // Make sure x escapes, and is therefore heap allocated
    }
}

func BenchmarkWithoutAlloc(b *testing.B) {

    a := []byte("abcde")
    x := make([]byte, 0, sizeTotal)

    for i := 0; i < b.N; i++ {
        x = x[:0]
        x = append(x, a...)
        x = append(x, a...)
        x = append(x, a...)
        x = append(x, a...)
        x = append(x, a...)
        res = x
    }
}

在我的盒子上,结果是:

testing: warning: no tests to run
PASS
BenchmarkWithAlloc      10000000               116 ns/op              32 B/op          1 allocs/op
BenchmarkWithoutAlloc   50000000                24.0 ns/op             0 B/op          0 allocs/op

系统地重新分配缓冲区(甚至是很小的缓冲区)会使此基准测试速度至少慢5倍。

因此,您最好希望对此代码进行优化,以确保您不会为所构建的每个数据包重新分配缓冲区。相反,您应该保留缓冲区,并在每次编组操作中重用它。

您可以重置切片,同时使用以下语句保留其基础缓冲区的分配:

x = x[:0]
2020-07-02