zoukankan      html  css  js  c++  java
  • String拼接效率分析

    先po一个基准测试结果

    package main
    
    import (
        "bytes"
        "fmt"
        "strings"
        "testing"
    )
    
    const v = "Measure the elapsed time between sending a data octet with a?"
    
    func BenchmarkStringJoin(b *testing.B) {
        var s string
        for i := 0; i < b.N; i++ {
            s = strings.Join([]string{s, "[", v, "]"}, "")
        }
    }
    
    func BenchmarkStringAdd(b *testing.B) {
        var s string
        for i := 0; i < b.N; i++ {
            s = s + "[" + v + "]"
        }
    }
    
    func BenchmarkSprintf(b *testing.B) {
        var s string
        for i := 0; i < b.N; i++ {
            s = fmt.Sprintf("%s[%s]", s, v)
        }
    }
    
    func BenchmarkBuffer(b *testing.B) {
        var buf bytes.Buffer
        for i := 0; i < b.N; i++ {
            buf.WriteString("[")
            buf.WriteString(v)
            buf.WriteString("]")
        }
    }
    BenchmarkStringJoin-8              18505            120331 ns/op
    BenchmarkStringAdd-8               25566            129192 ns/op
    BenchmarkSprintf-8                 12670            126964 ns/op
    BenchmarkBuffer-8               15034540               125 ns/op

    可以看到bytes.Buffer明显效率高于其他,下面简单分析一下。

    其实主要是String和byte[]的区别

    这里可以去看Rob Pike的一篇相关blog

    https://blog.golang.org/strings

    String

    type string
    
    string is the set of all strings of 8-bit bytes, conventionally but not necessarily representing UTF-8-encoded text. A string may be empty, but not nil. Values of string type are immutable.
    type stringStruct struct {
        str unsafe.Pointer
        len int
    }

    可以看到其实就是一个指向底层数组的指针,该数组的长度是len。

    func gostringnocopy(str *byte) string {
        ss := stringStruct{str: unsafe.Pointer(str), len: findnull(str)}
        s := *(*string)(unsafe.Pointer(&ss))
        return s
    }

    这就是新建字符串的时候,如果我们看string拼接的汇编,就会发现这个函数调用。

    []byte

    type slice struct {
        array unsafe.Pointer
        len   int
        cap   int
    }

    看起来很像,但是还是有区别的,可以去看一些这块的底层分析

    https://draveness.me/golang/docs/part2-foundation/ch03-datastructure/golang-array-and-slice/

    区别最常讲的就是string不可变,其实是因为这里的string本身是一个stringStruct{str: str_point, len: str_len}

    我们不能在地址上修改,但是可以换一个地址,这样也就会给gc增加任务和多分配一次内存。

    s := "A1" // 分配存储"A1"的内存空间,s结构体里的str指针指向这快内存
    s = "A2"  // 重新给"A2"的分配内存空间,s结构体里的str指针指向这快内存
    s := []byte{1} // 分配存储1数组的内存空间,s结构体的array指针指向这个数组。
    s = []byte{2}  // 将array的内容改为2

    转换

    只要牵扯转换,其实都会有内存的“浪费”,不难理解,因为string操作本身不可避免这个问题,上面分析了。

    string->[]byte

    func stringtoslicebyte(buf *tmpBuf, s string) []byte {
        var b []byte
        if buf != nil && len(s) <= len(buf) {
            *buf = tmpBuf{}
            b = buf[:len(s)]
        } else {
            b = rawbyteslice(len(s))
        }
        copy(b, s)
        return b
    }

    可以看到b是新分配的,然后再将s复制给b。其中这个copy()也是一个slicestringcopy()实现

    func slicestringcopy(to []byte, fm string) int {
        if len(fm) == 0 || len(to) == 0 {
            return 0
        }
    
        n := len(fm)
        if len(to) < n {
            n = len(to)
        }
    
        if raceenabled {
            callerpc := getcallerpc()
            pc := funcPC(slicestringcopy)
            racewriterangepc(unsafe.Pointer(&to[0]), uintptr(n), callerpc, pc)
        }
        if msanenabled {
            msanwrite(unsafe.Pointer(&to[0]), uintptr(n))
        }
    
        memmove(unsafe.Pointer(&to[0]), stringStructOf(&fm).str, uintptr(n))
        return n
    }

    可以看出没有复用内存。

    []byte->string

    func slicebytetostring(buf *tmpBuf, b []byte) string {
        l := len(b)
        if l == 0 {
            // Turns out to be a relatively common case.
            // Consider that you want to parse out data between parens in "foo()bar",
            // you find the indices and convert the subslice to string.
            return ""
        }
        if raceenabled && l > 0 {
            racereadrangepc(unsafe.Pointer(&b[0]),
                uintptr(l),
                getcallerpc(unsafe.Pointer(&buf)),
                funcPC(slicebytetostring))
        }
        if msanenabled && l > 0 {
            msanread(unsafe.Pointer(&b[0]), uintptr(l))
        }
        s, c := rawstringtmp(buf, l)
        copy(c, b)
        return s
    }

    一样的思路

    转换都没有复用内存,其实还是有些消耗的。

    boya列出了一个复用的思路

    func stringtoslicebyte(s string) []byte {
        sh := (*reflect.StringHeader)(unsafe.Pointer(&s))
        bh := reflect.SliceHeader{
            Data:sh.Data,
            Len:sh.Len,
            Cap:sh.Len,
        }
        return *(*[]byte)(unsafe.Pointer(&bh))
    }
    
    func slicebytetostring(b []byte) string {
        bh := (*reflect.SliceHeader)(unsafe.Pointer(&b))
        sh := reflect.StringHeader{
            Data: bh.Data,
            Len:  bh.Len,
        }
        return *(*string)(unsafe.Pointer(&sh))
    }

    这种就过于“自由”了。

    参考

    https://zboya.github.io/post/golang_byte_slice_and_string/

    一个没有高级趣味的人。 email:hushui502@gmail.com
  • 相关阅读:
    对象池使用时要注意几点
    Flash3D学习计划(一)——3D渲染的一般管线流程
    714. Best Time to Buy and Sell Stock with Transaction Fee
    712. Minimum ASCII Delete Sum for Two Strings
    647. Palindromic Substrings(马拉车算法)
    413. Arithmetic Slices
    877. Stone Game
    338. Counting Bits
    303. Range Sum Query
    198. House Robber
  • 原文地址:https://www.cnblogs.com/CherryTab/p/12835124.html
Copyright © 2011-2022 走看看