【问题标题】:What is the fastest way to concatenate several []byte together?将几个 [] 字节连接在一起的最快方法是什么?
【发布时间】:2015-11-28 23:49:04
【问题描述】:

现在我正在使用下面的代码(如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 连接在一起的最快方法是什么?

【问题讨论】:

  • 必须解码的数据大小是否可预测?
  • d1、d2、d3 的长度/大小始终为 16 个字节(它们是 UUID)。 d4 可以介于 16 字节和 1 MiB 之间。
  • 尝试写入容量初始化为所需长度的 bytes.Buffer,这样可以避免任何进一步的副本增长底层切片。

标签: performance optimization go concatenation slice


【解决方案1】:

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,则性能方面最好不要连接它们,而是分别将它们写入其中。

【讨论】:

  • 我刚刚对您的两个建议进行了基准测试,它们确实比我现在的要快。 JoinSize() 是迄今为止最快的函数。与 117 ns/op 相比,其速度为 101 ns/op。
  • 想想你可以用额外的 8ns 做的所有事情! /s ;)
  • @elithrar 这是 16 ns!执行它 1.8 次台球 (1.8e12),现在你有一个额外的假期!
【解决方案2】:

总的来说,@icza 的回答是正确的。但是,对于您的特定用例,您可以分配一次并更有效地解码到该缓冲区:

Like this:

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 个数据块,连续解码。

【讨论】:

  • 我在测试中使用十六进制编码字节作为数据样本。我实际上并没有在实际代码中使用 hex.DecodeString(),这就是为什么它们不是基准函数的一部分。
  • 如果我们可以指定我们想要结果的目标切片(就像您对 hex.Decode() 所做的那样),这个想法很好。但请注意,即使是这样,如果h4 很小(例如只有几个字节),这个解决方案可能效率很低,因为它分配了一个~1MB 的切片并且将使用大约 50 个字节(结果)。另请注意,即使您在最后对其进行了重新切片,只要切片存在,整个 1 MB 数组将保留在内存中。如果h4 很大,那么这是一个很好的优化。
  • 他们要求最快方法。
猜你喜欢
  • 2011-07-01
  • 1970-01-01
  • 2014-03-19
  • 1970-01-01
  • 2010-10-01
  • 1970-01-01
  • 2011-06-26
  • 1970-01-01
  • 2011-11-14
相关资源
最近更新 更多