• 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
  • 相关阅读:
    android中给TextView或者Button的文字添加阴影效果
    android:layout_weight详解
    android Button 颜色的变化(点击,放开,点击不放)
    Android之最简单的ImageView加边框方法
    泳道图
    使用Navicat生成ER关系图并导出
    IDEA须知
    Error running Tomcat8: Address localhost:1099 is already in use(IDEA错误)
    3分钟打动投资人:商业计划书篇
    HTML按钮属性
  • 原文地址:https://www.cnblogs.com/CherryTab/p/12835124.html
Copyright © 2020-2023  润新知