连接字节数组

huangapple go评论83阅读模式
英文:

Concat byte arrays

问题

以下是代码的更高效版本:

func (m *Message) ToByte() []byte {
	sizeTotal := 21 + len(m.callbackId) + len(m.target) + len(m.action) + len(m.content)
	b := make([]byte, sizeTotal)
	offset := 0

	binary.LittleEndian.PutUint32(b[offset:offset+4], uint32(sizeTotal))
	offset += 4

	b[offset] = byte(m.contentType)
	offset++

	binary.LittleEndian.PutUint32(b[offset:offset+4], uint32(len(m.callbackId)))
	offset += 4

	binary.LittleEndian.PutUint32(b[offset:offset+4], uint32(len(m.target)))
	offset += 4

	binary.LittleEndian.PutUint32(b[offset:offset+4], uint32(len(m.action)))
	offset += 4

	binary.LittleEndian.PutUint32(b[offset:offset+4], uint32(len(m.content)))
	offset += 4

	copy(b[offset:offset+len(m.callbackId)], []byte(m.callbackId))
	offset += len(m.callbackId)

	copy(b[offset:offset+len(m.target)], []byte(m.target))
	offset += len(m.target)

	copy(b[offset:offset+len(m.action)], []byte(m.action))
	offset += len(m.action)

	copy(b[offset:offset+len(m.content)], []byte(m.content))

	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}
}

这个版本中,我们使用了一个更大的字节数组 b 来存储结果,避免了多次扩容和拷贝的操作。同时,我们使用了一个 offset 变量来跟踪写入的位置,避免了重复计算偏移量。这样可以提高代码的效率。

注意:这只是对给定代码的优化版本,具体的性能提升还需要根据实际情况进行测试和评估。

英文:

Can someone please point at a more efficient version of the following

    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...)

every variable is a byte slice apart from size sizeTotal

Update:

Code:

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

Bench results:

   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

答案1

得分: 4

提供的内存已经分配好了,在Go语言中,使用x=append(x,a...)的序列是相当高效的。

在你的示例中,初始分配(make)的成本可能比追加的序列更高。这取决于字段的大小。考虑以下基准测试:

package main
import (
"testing"
)
const sizeTotal = 25
var res []byte // 强制堆分配
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 // 确保x逃逸,因此在堆上分配
}
}
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]
英文:

Provided memory is already allocated, a sequence of x=append(x,a...) is rather efficient in Go.

In your example, the initial allocation (make) probably costs more than the sequence of appends. It depends on the size of the fields. Consider the following benchmark:

package main
import (
&quot;testing&quot;
)
const sizeTotal = 25
var res []byte // To enforce heap allocation
func BenchmarkWithAlloc(b *testing.B) {
a := []byte(&quot;abcde&quot;)
for i := 0; i &lt; 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(&quot;abcde&quot;)
x := make([]byte, 0, sizeTotal)
for i := 0; i &lt; 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
}
}

On my box, the result is:

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

Systematically reallocating the buffer (even a small one) makes this benchmark at least 5 times slower.

So your best hope to optimize this code it to make sure you do not reallocate a buffer for each packet you build. On the contrary, you should keep your buffer, and reuse it for each marshalling operation.

You can reset a slice while keeping its underlying buffer allocated with the following statement:

x = x[:0]

答案2

得分: 2

我仔细观察了这段代码,并进行了以下基准测试。

package append

import "testing"

func BenchmarkAppend(b *testing.B) {
    as := 1000
    a := make([]byte, as)
    s := make([]byte, 0, b.N*as)
    for i := 0; i < b.N; i++ {
        s = append(s, a...)
    }
}

func BenchmarkCopy(b *testing.B) {
    as := 1000
    a := make([]byte, as)
    s := make([]byte, 0, b.N*as)
    for i := 0; i < b.N; i++ {
        copy(s[i*as:(i+1)*as], a)
    }
}

结果如下:

grzesiek@klapacjusz ~/g/s/t/append> go test -bench . -benchmem
testing: warning: no tests to run
PASS
BenchmarkAppend 10000000 202 ns/op 1000 B/op 0 allocs/op
BenchmarkCopy 10000000 201 ns/op 1000 B/op 0 allocs/op
ok test/append 4.564s

如果totalSize足够大,那么你的代码不会进行任何内存分配。它只会复制需要复制的字节数量。这是完全正常的。

英文:

I looked carefully at that and made the following benchmarks.

package append
import &quot;testing&quot;
func BenchmarkAppend(b *testing.B) {
as := 1000
a := make([]byte, as)
s := make([]byte, 0, b.N*as)
for i := 0; i &lt; b.N; i++ {
s = append(s, a...)
}
}
func BenchmarkCopy(b *testing.B) {
as := 1000
a := make([]byte, as)
s := make([]byte, 0, b.N*as)
for i := 0; i &lt; b.N; i++ {
copy(s[i*as:(i+1)*as], a)
}
}

The results are

grzesiek@klapacjusz ~/g/s/t/append&gt; go test -bench . -benchmem
testing: warning: no tests to run
PASS
BenchmarkAppend	10000000	       202 ns/op	    1000 B/op	       0 allocs/op
BenchmarkCopy	10000000	       201 ns/op	    1000 B/op	       0 allocs/op
ok  	test/append	4.564s

If the totalSize is big enough then your code makes no memory allocations. It copies only the amount of bytes it needs to copy. It is perfectly fine.

huangapple
  • 本文由 发表于 2015年3月14日 16:24:20
  • 转载请务必保留本文链接:https://go.coder-hub.com/29046963.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定