doushuichong2589
2019-08-09 22:55
浏览 68
已采纳

为什么在测试程序中添加string时,strings.Builder比fmt.Sprint慢?

I'm trying to optimize speed of a String() function of struct FmtC. Based on following benchmark.

  • If I pre-allocate 1024 bytes with strings.Builder, it is slower (624 ns) than fmt.Sprint (437 ns).
  • If I pre-allocate 32 bytes, strings.Builder is faster (74.8 ns) but it is not useful if FmtC contains more member fields.
  • If I pre-allocate 1024 bytes by slice append method it is slowest (2337 ns).

go version: go1.12.7 linux/amd64.

go test -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

main.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()
}

(Edit:) After changing the mistake of s := make([]byte, preAllocatedSize) to s := make([]byte, 0, preAllocatedSize), the result is: only pre-allocated 1024 bytes version is slower than fmt.Sprint in this simple test.

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
  • 点赞
  • 写回答
  • 关注问题
  • 收藏
  • 邀请回答

1条回答 默认 最新

  • duanbing2963 2019-08-09 23:21
    已采纳

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

    Your first error is that your code doesn't compile.

    BenchmarkStringsBuilder_128 redeclared in this block
    

    go test -v -bench=.

    Your second error is not using go test option -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
    

    You may find this function is faster than your functions.

    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)
    }
    
    点赞 打赏 评论

相关推荐 更多相似问题