将几个 []byte 连接在一起的最快方法是什么?
What is the fastest way to concatenate several []byte together?
现在我正在使用下面的代码(如 BenchmarkEncoder()
),它很快,但我想知道是否有更快、更有效的方法。我使用 GOMAXPROCS=1
和:
进行基准测试
sudo -E nice -n -20 go test -bench . -benchmem -benchtime 3s
.
package blackbird
import (
"testing"
"encoding/hex"
"log"
"bytes"
"encoding/json"
)
var (
d1, d2, d3, d4, outBytes []byte
toEncode [][]byte
)
func init() {
var err interface{}
d1, err = hex.DecodeString("6e5438fd9c3748868147d7a4f6d355dd")
d2, err = hex.DecodeString("0740e2dfa4b049f2beeb29cc304bdb5f")
d3, err = hex.DecodeString("ab6743272358467caff7d94c3cc58e8c")
d4, err = hex.DecodeString("7411c080762a47f49e5183af12d87330e6d0df7dd63a44808db4e250cdea0a36182fce4a309842e49f4202eb90184dd5b621d67db4a04940a29e981a5aea59be")
if err != nil {
log.Fatal("hex decoding failed: %v", err)
}
toEncode = [][]byte{d1, d2, d3, d4}
}
func Encode(stuff [][]byte) []byte {
return bytes.Join(stuff, nil)
}
func BenchmarkEncoderDirect(b *testing.B) {
for i := 0; i < b.N; i++ {
bytes.Join(toEncode, nil)
}
}
func BenchmarkEncoder(b *testing.B) {
for i := 0; i < b.N; i++ {
Encode(toEncode)
}
}
func BenchmarkJsonEncoder(b *testing.B) {
for i := 0; i < b.N; i++ {
outBytes, _ = json.Marshal(toEncode)
}
}
将多个 []byte
连接在一起的最快方法是什么?
bytes.Join()
非常快,但它在可附加字节片之间附加分隔符做了一些额外的工作。即使分隔符是空的或 nil
切片,它也会这样做。
因此,如果您关心最佳性能(尽管它会略有改进),您可以执行 bytes.Join()
所做的而不附加(空)分隔符:分配一个足够大的字节片,然后复制使用内置 copy()
函数对结果进行切片。
在 Go Playground 上试试:
func Join(s ...[]byte) []byte {
n := 0
for _, v := range s {
n += len(v)
}
b, i := make([]byte, n), 0
for _, v := range s {
i += copy(b[i:], v)
}
return b
}
使用它:
concatenated := Join(d1, d2, d3, d4)
改进:
如果您事先知道总大小(或者您可以比遍历切片更快地计算它),请提供它,您可以避免必须遍历切片才能计算所需的大小:
func JoinSize(size int, s ...[]byte) []byte {
b, i := make([]byte, size), 0
for _, v := range s {
i += copy(b[i:], v)
}
return b
}
在你的案例中使用它:
concatenated := JoinSize(48 + len(d4), d1, d2, d3, d4)
备注:
但如果你最终的目标是将连接的字节切片写入 io.Writer
,性能方面最好不要连接它们,而是分别写入每个切片。
总的来说,@icza 的回答是正确的。但是,对于您的特定用例,您可以分配一次并更有效地解码到该缓冲区中:
package main
import (
"encoding/hex"
)
func main() {
h1 := []byte("6e5438fd9c3748868147d7a4f6d355dd")
h2 := []byte("0740e2dfa4b049f2beeb29cc304bdb5f")
h3 := []byte("ab6743272358467caff7d94c3cc58e8c")
h4 := []byte("7411c080762a47f49e5183af12d87330e6d0df7dd63a44808db4e250cdea0a36182fce4a309842e49f4202eb90184dd5b621d67db4a04940a29e981a5aea59be")
tg := make([]byte, 16+16+16+(1024*1024)) // allocate enough space for the 3 IDs and a max 1MB of extra data
hex.Decode(tg[:16], h1)
hex.Decode(tg[16:32], h2)
hex.Decode(tg[32:48], h3)
l, _ := hex.Decode(tg[48:], h4)
tg = tg[:48+l]
}
在该代码的末尾,tg
包含 3 个 ID 加上可变长度的第 4 个数据块,已连续解码。
现在我正在使用下面的代码(如 BenchmarkEncoder()
),它很快,但我想知道是否有更快、更有效的方法。我使用 GOMAXPROCS=1
和:
sudo -E nice -n -20 go test -bench . -benchmem -benchtime 3s
.
package blackbird
import (
"testing"
"encoding/hex"
"log"
"bytes"
"encoding/json"
)
var (
d1, d2, d3, d4, outBytes []byte
toEncode [][]byte
)
func init() {
var err interface{}
d1, err = hex.DecodeString("6e5438fd9c3748868147d7a4f6d355dd")
d2, err = hex.DecodeString("0740e2dfa4b049f2beeb29cc304bdb5f")
d3, err = hex.DecodeString("ab6743272358467caff7d94c3cc58e8c")
d4, err = hex.DecodeString("7411c080762a47f49e5183af12d87330e6d0df7dd63a44808db4e250cdea0a36182fce4a309842e49f4202eb90184dd5b621d67db4a04940a29e981a5aea59be")
if err != nil {
log.Fatal("hex decoding failed: %v", err)
}
toEncode = [][]byte{d1, d2, d3, d4}
}
func Encode(stuff [][]byte) []byte {
return bytes.Join(stuff, nil)
}
func BenchmarkEncoderDirect(b *testing.B) {
for i := 0; i < b.N; i++ {
bytes.Join(toEncode, nil)
}
}
func BenchmarkEncoder(b *testing.B) {
for i := 0; i < b.N; i++ {
Encode(toEncode)
}
}
func BenchmarkJsonEncoder(b *testing.B) {
for i := 0; i < b.N; i++ {
outBytes, _ = json.Marshal(toEncode)
}
}
将多个 []byte
连接在一起的最快方法是什么?
bytes.Join()
非常快,但它在可附加字节片之间附加分隔符做了一些额外的工作。即使分隔符是空的或 nil
切片,它也会这样做。
因此,如果您关心最佳性能(尽管它会略有改进),您可以执行 bytes.Join()
所做的而不附加(空)分隔符:分配一个足够大的字节片,然后复制使用内置 copy()
函数对结果进行切片。
在 Go Playground 上试试:
func Join(s ...[]byte) []byte {
n := 0
for _, v := range s {
n += len(v)
}
b, i := make([]byte, n), 0
for _, v := range s {
i += copy(b[i:], v)
}
return b
}
使用它:
concatenated := Join(d1, d2, d3, d4)
改进:
如果您事先知道总大小(或者您可以比遍历切片更快地计算它),请提供它,您可以避免必须遍历切片才能计算所需的大小:
func JoinSize(size int, s ...[]byte) []byte {
b, i := make([]byte, size), 0
for _, v := range s {
i += copy(b[i:], v)
}
return b
}
在你的案例中使用它:
concatenated := JoinSize(48 + len(d4), d1, d2, d3, d4)
备注:
但如果你最终的目标是将连接的字节切片写入 io.Writer
,性能方面最好不要连接它们,而是分别写入每个切片。
总的来说,@icza 的回答是正确的。但是,对于您的特定用例,您可以分配一次并更有效地解码到该缓冲区中:
package main
import (
"encoding/hex"
)
func main() {
h1 := []byte("6e5438fd9c3748868147d7a4f6d355dd")
h2 := []byte("0740e2dfa4b049f2beeb29cc304bdb5f")
h3 := []byte("ab6743272358467caff7d94c3cc58e8c")
h4 := []byte("7411c080762a47f49e5183af12d87330e6d0df7dd63a44808db4e250cdea0a36182fce4a309842e49f4202eb90184dd5b621d67db4a04940a29e981a5aea59be")
tg := make([]byte, 16+16+16+(1024*1024)) // allocate enough space for the 3 IDs and a max 1MB of extra data
hex.Decode(tg[:16], h1)
hex.Decode(tg[16:32], h2)
hex.Decode(tg[32:48], h3)
l, _ := hex.Decode(tg[48:], h4)
tg = tg[:48+l]
}
在该代码的末尾,tg
包含 3 个 ID 加上可变长度的第 4 个数据块,已连续解码。