go - 为什么 strings.Builder 在我的测试程序中追加字符串比 fmt.Sprint 慢?

标签 go

我正在尝试优化结构 FmtCString() 函数的速度。基于以下基准。

  • 如果我使用 strings.Builder 预分配 1024 字节,它比 fmt.Sprint(437 ns)慢(624 ns)。
  • 如果我预先分配 32 字节,strings.Builder 会更快(74.8 ns),但如果 FmtC 包含更多成员字段,它就没有用了。
  • 如果我通过 slice 追加方法预分配 1024 字节,它是最慢的 (2337 ns)。

go版本:go1.12.7 linux/amd64。

去测试 -v -bench=。 -benchmem

BenchmarkFmtSprint_32-2                  3000000           435 ns/op          64 B/op          4 allocs/op
BenchmarkStringsBuilder_32-2            20000000            74.8 ns/op        32 B/op          1 allocs/op
BenchmarkSliceAppendString_32-2         10000000           213 ns/op         160 B/op          3 allocs/op
BenchmarkSliceAppendBytes_32-2          10000000           143 ns/op          96 B/op          2 allocs/op
BenchmarkFmtSprint_128-2                 3000000           437 ns/op          64 B/op          4 allocs/op
BenchmarkStringsBuilder_128-2           10000000           125 ns/op         128 B/op          1 allocs/op
BenchmarkSliceAppendString_128-2         3000000           478 ns/op         544 B/op          3 allocs/op
BenchmarkSliceAppendBytes_128-2          5000000           312 ns/op         384 B/op          2 allocs/op
BenchmarkFmtSprint_1024-2                3000000           437 ns/op          64 B/op          4 allocs/op
BenchmarkStringsBuilder_1024-2           2000000           624 ns/op        1024 B/op          1 allocs/op
BenchmarkSliceAppendString_1024-2         500000          2337 ns/op        3456 B/op          3 allocs/op
BenchmarkSliceAppendBytes_1024-2         5000000           310 ns/op         384 B/op          2 allocs/op

主.go

package main

import (
        "fmt"
        "strconv"
        "strings"
)

type FmtC struct {
        Field1 uint32
        Field2 [5]byte
}

var preAllocatedSize = 1024

func (c FmtC) FmtSprint() string {
        return fmt.Sprint("{Field1:", c.Field1, " Field2:",
                string(c.Field2[:]), "}")
}
func (c FmtC) StringsBuilder() string {
        var s strings.Builder
        s.Grow(preAllocatedSize) // output length width less than 1024 bytes
        s.WriteString("{Field1:")
        s.WriteString(strconv.FormatUint(uint64(c.Field1), 10))
        s.WriteString(" Field2:")
        s.Write(c.Field2[:])
        s.WriteString("}")
        return s.String()
}
func (c FmtC) SliceAppendString() string {
        s := make([]byte, preAllocatedSize)
        s = append(s, "{Field1:"...)
        s = strconv.AppendUint(s, uint64(c.Field1), 10)
        s = append(s, " Field2:"...)
        s = append(s, c.Field2[:]...)
        s = append(s, "}"...)
        return string(s)
}
func (c FmtC) SliceAppendBytes() []byte {
        s := make([]byte, preAllocatedSize)
        s = append(s, "{Field1:"...)
        s = strconv.AppendUint(s, uint64(c.Field1), 10)
        s = append(s, " Field2:"...)
        s = append(s, c.Field2[:]...)
        s = append(s, "}"...)
        return s
}
func main() {
}

main_test.go

package main

import (
        "testing"
)

var c = FmtC{5, [5]byte{'h', 'e', 'l', 'l', 'o'}}

func BenchmarkFmtSprint_32(b *testing.B) {
        preAllocatedSize = 32
        for n := 0; n < b.N; n++ {
                c.FmtSprint()
        }
        b.StopTimer()
}
func BenchmarkStringsBuilder_32(b *testing.B) {
        preAllocatedSize = 32
        for n := 0; n < b.N; n++ {
                c.StringsBuilder()
        }
        b.StopTimer()
}
func BenchmarkSliceAppendString_32(b *testing.B) {
        preAllocatedSize = 32
        for n := 0; n < b.N; n++ {
                c.SliceAppendString()
        }
        b.StopTimer()
}
func BenchmarkSliceAppendBytes_32(b *testing.B) {
        preAllocatedSize = 32
        for n := 0; n < b.N; n++ {
                c.SliceAppendBytes()
        }
        b.StopTimer()
}
func BenchmarkFmtSprint_128(b *testing.B) {
        preAllocatedSize = 128
        for n := 0; n < b.N; n++ {
                c.FmtSprint()
        }
        b.StopTimer()
}
func BenchmarkStringsBuilder_128(b *testing.B) {
        preAllocatedSize = 128
        for n := 0; n < b.N; n++ {
                c.StringsBuilder()
        }
        b.StopTimer()
}
func BenchmarkSliceAppendString_128(b *testing.B) {
        preAllocatedSize = 128
        for n := 0; n < b.N; n++ {
                c.SliceAppendString()
        }
        b.StopTimer()
}
func BenchmarkSliceAppendBytes_128(b *testing.B) {
        preAllocatedSize = 128
        for n := 0; n < b.N; n++ {
                c.SliceAppendBytes()
        }
        b.StopTimer()
}
func BenchmarkFmtSprint_1024(b *testing.B) {
        preAllocatedSize = 1024
        for n := 0; n < b.N; n++ {
                c.FmtSprint()
        }
        b.StopTimer()
}
func BenchmarkStringsBuilder_1024(b *testing.B) {
        preAllocatedSize = 1024
        for n := 0; n < b.N; n++ {
                c.StringsBuilder()
        }
        b.StopTimer()
}
func BenchmarkSliceAppendString_1024(b *testing.B) {
        preAllocatedSize = 1024
        for n := 0; n < b.N; n++ {
                c.SliceAppendString()
        }
        b.StopTimer()
}
func BenchmarkSliceAppendBytes_1024(b *testing.B) {
        preAllocatedSize = 128
        for n := 0; n < b.N; n++ {
                c.SliceAppendBytes()
        }
        b.StopTimer()
}

(编辑:)将s := make([]byte, preAllocatedSize)的错误改成s := make([]byte, 0, preAllocatedSize),结果是:在这个简单的测试中,只有预分配的 1024 字节版本比 fmt.Sprint 慢。

BenchmarkFmtSprint_32-2                  3000000           432 ns/op          64 B/op          4 allocs/op
BenchmarkStringsBuilder_32-2            20000000            75.2 ns/op        32 B/op          1 allocs/op
BenchmarkSliceAppendString_32-2         20000000           112 ns/op          64 B/op          2 allocs/op
BenchmarkSliceAppendBytes_32-2          20000000            64.2 ns/op        32 B/op          1 allocs/op
BenchmarkFmtSprint_128-2                 3000000           437 ns/op          64 B/op          4 allocs/op
BenchmarkStringsBuilder_128-2           10000000           123 ns/op         128 B/op          1 allocs/op
BenchmarkSliceAppendString_128-2        10000000           162 ns/op         160 B/op          2 allocs/op
BenchmarkSliceAppendBytes_128-2         20000000           110 ns/op         128 B/op          1 allocs/op
BenchmarkFmtSprint_1024-2                3000000           429 ns/op          64 B/op          4 allocs/op
BenchmarkStringsBuilder_1024-2           2000000           626 ns/op        1024 B/op          1 allocs/op
BenchmarkSliceAppendString_1024-2        2000000           653 ns/op        1056 B/op          2 allocs/op
BenchmarkSliceAppendBytes_1024-2        10000000           110 ns/op         128 B/op          1 allocs/op

最佳答案

main_test.go:

func BenchmarkStringsBuilder_128(b *testing.B) {
        preAllocatedSize = 128
        for n := 0; n < b.N; n++ {
                c.StringsBuilder()
        }
        b.StopTimer()
}
func BenchmarkStringsBuilder_128(b *testing.B) {
        preAllocatedSize = 128
        for n := 0; n < b.N; n++ {
                c.StringsBuilder()
        }
        b.StopTimer()
}

您的第一个错误是您的代码无法编译。

BenchmarkStringsBuilder_128 redeclared in this block

go test -v -bench=.

你的第二个错误是没有使用 go test 选项 -benchmem

$ go version
go version devel +9c1f14f376 Fri Aug 9 20:26:42 2019 +0000 linux/amd64

$ go test -bench=. -benchmem

BenchmarkFmtSprint_32-8                  5116165   207 ns/op       64 B/op   4 allocs/op
BenchmarkStringsBuilder_32-8            34339864    37.1 ns/op     32 B/op   1 allocs/op
BenchmarkSliceAppendString_32-8         12525960    85.5 ns/op    160 B/op   3 allocs/op
BenchmarkSliceAppendBytes_32-8          17084019    62.0 ns/op     96 B/op   2 allocs/op
BenchmarkFmtSprint_128-8                 5681800   205 ns/op       64 B/op   4 allocs/op
BenchmarkStringsBuilder_128-8           26086238    46.3 ns/op    128 B/op   1 allocs/op
BenchmarkSliceAppendString_128-8         9424910   126 ns/op      544 B/op   3 allocs/op
BenchmarkSliceAppendBytes_128-8         13260948    88.7 ns/op    384 B/op   2 allocs/op
BenchmarkFmtSprint_1024-8                5536604   205 ns/op       64 B/op   4 allocs/op
BenchmarkStringsBuilder_1024-8           8897110   133 ns/op     1024 B/op   1 allocs/op
BenchmarkSliceAppendString_1024-8        2764279   433 ns/op     3456 B/op   3 allocs/op
BenchmarkSliceAppendBytes_1024-8        13479661    88.6 ns/op    384 B/op   2 allocs/op

您可能会发现此函数比您的函数更快。

import (
    "strconv"
)

type FmtC struct {
    Field1 uint32
    Field2 [5]byte
}

func (c FmtC) String() string {
    s := make([]byte, 0, 32)
    s = append(s, "{Field1:"...)
    s = strconv.AppendUint(s, uint64(c.Field1), 10)
    s = append(s, " Field2:"...)
    s = append(s, c.Field2[:]...)
    s = append(s, "}"...)
    return string(s)
}

关于go - 为什么 strings.Builder 在我的测试程序中追加字符串比 fmt.Sprint 慢?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/57437958/

相关文章:

macos - 无法在 OSX 上使用 docker 公开 Web 服务器

go - Golang行情自动收录机并发问题

tcp - Golang 从 net.TCPConn 读取字节

file - 为什么在Go中写入已删除的文件不会返回错误?

google-app-engine - 初始化和销毁​​函数

Go找不到本地包

Golang 中的 Json 解码/解码

sql - lib/pq 连接但查询因连接不良而失败

go - 如何创建一个包罗万象的 Go 环境以满足提出的要求 : "application must compile on shared server"

go - 追加到长度未知的大数组的最佳执行方式