内置的append和bytes.Buffer的write方法的区别

8
在需要向一个字节切片添加未知数量的数据的情况下,比如在一个循环中,我既可以使用内建函数append(),也可以创建一个新的Buffer并使用Write()函数。哪种方法更快?

内置函数通常比任何替代方案都要快。我猜在这种情况下,使用append会更快。 - Sridhar
2个回答

10
这取决于使用情况。在这两种情况下,bytes.Bufferappend更快(样本:1、2、3、4)。
使用buf.Write(make([]byte, 16))需要4.6482659秒, 使用buf = append(buf, make([]byte, 16)...)需要6.6623811秒
对于样本5、6: 使用buf = append(buf, byte(i))需要445.0255毫秒, 使用buf.WriteByte(byte(i))需要1.4410824秒
而且bytes.Buffer使用内置函数copy,速度很快。

// Write appends the contents of p to the buffer, growing the buffer as
// needed. The return value n is the length of p; err is always nil. If the
// buffer becomes too large, Write will panic with ErrTooLarge.

func (b *Buffer) Write(p []byte) (n int, err error) {
  b.lastRead = opInvalid
  m := b.grow(len(p))
  return copy(b.buf[m:], p), nil
}

bytes.Buffer花费了4.8892797秒,而append花费了7.7514434秒。

请查看以下基准测试结果:

1- 使用append

package main

import (
    "fmt"
    "time"
)

func main() {
    buf := []byte{}
    data := []byte{1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16}
    t := time.Now()
    for i := 0; i < 100000000; i++ {
        buf = append(buf, data...)
    }
    fmt.Println(time.Since(t))
    fmt.Println(len(buf))
}

输出:

7.7514434s
1600000000

2- 使用 bytes.Buffer

package main

import (
    "bytes"
    "fmt"
    "time"
)

func main() {
    buf := &bytes.Buffer{}
    data := []byte{1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16}
    t := time.Now()
    for i := 0; i < 100000000; i++ {
        buf.Write(data)
    }
    fmt.Println(time.Since(t))
    fmt.Println(buf.Len())
}

输出:

4.8892797s
1600000000

3- 使用 bytes.Buffermake([]byte, 16)

package main

import (
    "bytes"
    "fmt"
    "time"
)

func main() {
    buf := &bytes.Buffer{}
    t := time.Now()
    for i := 0; i < 100000000; i++ {
        buf.Write(make([]byte, 16))
    }
    fmt.Println(time.Since(t)) // 4.6482659s
    fmt.Println(buf.Len())     //1600000000
}

4- 使用 make([]byte, 16) 来配合 append

package main

import (
    "fmt"
    "time"
)

func main() {
    buf := []byte{}
    t := time.Now()
    for i := 0; i < 100000000; i++ {
        buf = append(buf, make([]byte, 16)...)
    }
    fmt.Println(time.Since(t)) // 6.6623811s
    fmt.Println(len(buf))      // 1600000000
}

5- 使用 buf = append(buf, byte(i)) 耗时 445.0255ms:

package main

import (
    "fmt"
    "time"
)

func main() {
    buf := []byte{}
    t := time.Now()
    for i := 0; i < 100000000; i++ {
        buf = append(buf, byte(i))
    }
    fmt.Println(time.Since(t)) // 445.0255ms
    fmt.Println(len(buf))      // 100000000
}

6- 使用 buf.WriteByte(byte(i)) 耗时 1.4410824s:

package main

import (
    "bytes"
    "fmt"
    "time"
)

func main() {
    buf := &bytes.Buffer{}

    t := time.Now()
    for i := 0; i < 100000000; i++ {
        buf.WriteByte(byte(i))
    }
    fmt.Println(time.Since(t)) // 1.4410824s
    fmt.Println(buf.Len())     // 100000000
}

请参见:

为什么向切片追加元素会影响性能?
append() 的实现在哪里?
如何高效地向可变长度的字符串容器中追加元素(Golang)


看到两个答案的结果不同,我根据自己的需求尝试编写了自己的代码:https://play.golang.org/p/kyuX5qzaIG。结果显示,append 是最快的(append = 2.70 ns/op,bufferWrite = 9.32 ns/op)。你的测试肯定有问题。 - William Poussier
@WilliamPoussier:在Tim Cooper的答案中,对于函数randomSlice,请尝试使用return make([]byte, rand.Intn(1<<10))而不是return make([]byte, 0, rand.Intn(1<<10))。使用长度为零的切片不是一个好的基准。 - user6169399
1
确实你是对的。当我把我的测试数据改成非空切片时,使用缓冲区比追加慢。 - William Poussier

1
使用内置函数append更快,如下基准测试所示:
package x

import (
    "bytes"
    "math/rand"
    "testing"
    "time"
)

var startSeed = time.Now().UnixNano()

func randomSlice() []byte {
    return make([]byte, 0, rand.Intn(1<<10))
}

func BenchmarkAppend(b *testing.B) {
    rand.Seed(startSeed)
    b.ResetTimer()
    var all []byte

    for i := 0; i < b.N; i++ {
        all = append(all, randomSlice()...)
    }
}

func BenchmarkBufferWrite(b *testing.B) {
    rand.Seed(startSeed)
    b.ResetTimer()
    var buff bytes.Buffer
    for i := 0; i < b.N; i++ {
        buff.Write(randomSlice())
    }
    all := buff.Bytes()
    _ = all
}

结果:

BenchmarkAppend-4           10000000           206 ns/op         540 B/op          0 allocs/op
BenchmarkBufferWrite-4      10000000           214 ns/op         540 B/op          0 allocs/op

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接