go 高性能数据

字符串高效拼接

常见方式:使用+  使用fmt.Sprintf 

效率比较高的:

  • strings.Builder

func builderConcat(n int, str string) string {
    var builder strings.Builder
    for i := 0; i < n; i++ {
        builder.WriteString(str)
    }
    return builder.String()
}
  • 使用 bytes.Buffer
func bufferConcat(n int, s string) string {
    buf := new(bytes.Buffer)
    for i := 0; i < n; i++ {
        buf.WriteString(s)
    }
    return buf.String()
}
  • 使用 []byte
func byteConcat(n int, str string) string {
    buf := make([]byte, 0)
    for i := 0; i < n; i++ {
        buf = append(buf, str...)
    }
    return string(buf)
}

go官方

// A Builder is used to efficiently build a string using Write methods.

 

数组:

Go 语言中,数组变量属于值类型(value type),因此当一个数组变量被赋值或者传递时,实际上会复制整个数组。例如,将 a 赋值给 b,修改 a 中的元素并不会改变 b 中的元素:

a := [...]int{1, 2, 3} // ... 会自动计算数组长度
b := a
a[0] = 100
fmt.Println(a, b) // [100 2 3] [1 2 3]

对于slice 注意引用导致大内存不能删除

for range

  range 在迭代过程中返回的是迭代值的拷贝,如果每次迭代的元素的内存占用很低,那么 for 和 range 的性能几乎是一样,例如 []int。但是如果迭代的元素内存占用较高,例如一个包含很多属性的 struct 结构体,那么 for 的性能将显著地高于 range,

有时候甚至会有上千倍的性能差异。对于这种场景,建议使用 for,如果使用 range,建议只迭代下标,通过下标访问迭代值,这种使用方式和 for 就没有区别了。

如果想使用 range 同时迭代下标和值,则需要将切片/数组的元素改为指针,才能不影响性能。

func BenchmarkForStruct(b *testing.B) {
    var items [1024]Item
    for i := 0; i < b.N; i++ {
        length := len(items)
        var tmp int
        for k := 0; k < length; k++ {
            tmp = items[k].id
        }
        _ = tmp
    }
}
type Item struct {
    id  int
    val [4096]byte
}

func BenchmarkForStruct(b *testing.B) {
    var items [1024]Item
    for i := 0; i < b.N; i++ {
        length := len(items)
        var tmp int
        for k := 0; k < length; k++ { // len for 方式
            tmp = items[k].id
        }
        _ = tmp
    }
}

func BenchmarkRangeIndexStruct(b *testing.B) {
    var items [1024]Item
    for i := 0; i < b.N; i++ {
        var tmp int
        for k := range items {. // 之rang key index
            tmp = items[k].id
        }
        _ = tmp
    }
}

func BenchmarkRangeStruct(b *testing.B) {
    var items [1024]Item
    for i := 0; i < b.N; i++ {
        var tmp int
        for _, item := range items {
            tmp = item.id
        }
        _ = tmp
    }
}

 Benchmark 的结果:

$ go test -bench=Struct$ .
goos: darwin
goarch: amd64
pkg: example/hpg-range
BenchmarkForStruct-8             3769580               324 ns/op
BenchmarkRangeIndexStruct-8      3597555               330 ns/op
BenchmarkRangeStruct-8              2194            467411 ns/op

 

string 和byte 转换

标准转换

go 中 string 与 []byte 的标准转换。

s1 := "hello"
b := []byte(s1)

// []byte to string
s2 := string(b)

string2[]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
}

[]byte2string:

// Buf is a fixed-size buffer for the result,
// it is not nil if the result does not escape.
func slicebytetostring(buf *tmpBuf, b []byte) (str 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 ""
    }
  // 如果开启了竞态检测 -race
    if raceenabled {
        racereadrangepc(unsafe.Pointer(&b[0]),
            uintptr(l),
            getcallerpc(),
            funcPC(slicebytetostring))
    }
  // 如果开启了 memory sanitizer -msan
    if msanenabled {
        msanread(unsafe.Pointer(&b[0]), uintptr(l))
    }
    if l == 1 {
        stringStructOf(&str).str = unsafe.Pointer(&staticbytes[b[0]])
        stringStructOf(&str).len = 1
        return
    }

    var p unsafe.Pointer
    if buf != nil && len(b) <= len(buf) {
        p = unsafe.Pointer(buf)
    } else {
        p = mallocgc(uintptr(len(b)), nil, false)
    }
    stringStructOf(&str).str = p
    stringStructOf(&str).len = len(b)
  // 拷贝字节数组至字符串
    memmove(p, (*(*slice)(unsafe.Pointer(&b))).array, uintptr(len(b)))
    return
}

// 实例 stringStruct 对象
func stringStructOf(sp *string) *stringStruct {
    return (*stringStruct)(unsafe.Pointer(sp))
}

强转换

通过 unsafe 和 reflect 包,

func String2Bytes(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 Bytes2String(b []byte) string {
    return *(*string)(unsafe.Pointer(&b))
}

 

  对于标准转换,无论是从 []byte 转 string 还是 string 转 []byte 都会涉及底层数组的拷贝。而强转换是直接替换指针的指向,从而使得 string 和 []byte 指向同一个底层数组。

所以强制转换性能更优!强转换的方式,会给我们的程序带来极大的安全隐患。

a := "hello"
b := String2Bytes(a)
b[0] = 'H'

  a 是 string 类型,前面我们讲到它的值是不可修改的。通过强转换将 a 的底层数组赋给 b,而 b 是一个 []byte 类型,它的值是可以修改的,

所以这时对底层数组的值进行修改,将会造成严重的错误(通过 defer + recover 也不能捕获)。

 

posted @ 2023-08-01 22:02  codestacklinuxer  阅读(25)  评论(0)    收藏  举报