0%

Golang性能优化篇

在编写代码时常使用且常忽视的性能问题进行总结。这篇文章将持续更新…

string 和 []byte 相互转换

无论是从哪种类型转换到另一种都需要对其中的内容进行拷贝,string 和 []byte 相互转换 也不例外,内存拷贝的性能损耗会随着字符串数组和字节长度的增长而增长,所以在做这种类型转换时一定要注意性能上的问题。

接下来我们可以使用一些方法来避免这种大批量的拷贝工作,这个方法就是可以绕过类型检查的 unsafe.Pointer

1
2
3
4
5
6
7
8
9
func str2bytes(s string) []byte {
x := (*[2]uintptr)(unsafe.Pointer(&s))
b := [3]uintptr{x[0], x[1], x[1]}
return *(*[]byte)(unsafe.Pointer(&b))
}

func bytes2str(b []byte) string {
return *(*string)(unsafe.Pointer(&b))
}

字符串连接

字符串连接有很多方法:

  1. 直接通过运算符+
  2. fmt.Sprintf()
  3. bytes.Buffer{}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
func Buffer(str ...string) string {
buffer := bytes.Buffer{}
n := 0
for _, s := range str {
n += len(s)
}

buffer.Grow(n)
for _, s := range str {
buffer.WriteString(s)
}
return buffer.String()
}

  1. strings.Builder{}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
func Builder(str ...string) string {
builder := strings.Builder{}
n := 0
for _, s := range str {
n += len(s)
}

builder.Grow(n)
for _, s := range str {
builder.WriteString(s)
}
return builder.String()
}

  1. copy方法
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
func Copy(str ...string) string {
switch len(str) {
case 0:
return ""
case 1:
return str[0]
}
n := 0
for _, s := range str {
n += len(Str2Bytes(s))
}
bytes := make([]byte, n)
c := 0
for _, s := range str {
c += copy(bytes[c:], Str2Bytes(s))
}
return Bytes2Str(bytes)
}

既然方法有这么多种,性能问题就会有差异,通过benchmark测试查看一下:

benchmark-2022021101

可以看出copy和strings.Builder性能上是最好的,fmt.Sprintf是最差的,为什么呢?要知道golang 里面的字符串都是不可变的,每次运算都会产生一个新的字符串,所以会产生很多临时的无用的字符串,不仅没有用,还会给 gc 带来额外的负担,所以性能变差。而copy和strings.Builder根据长度预先分配内存,没有产生很多临时的无用的字符串。本质上就是以空间换时间。

json序列化

json系列我们最常用的就是标准库里的encoding/json,但为提高性能可以使用一些第三方库替换。如json-iterator、easyjson等。

性能提升多少呢?我们通过banchmark测试看一下:

benchmark-2022021101

可以看到json-iterator和easyjson相比easyjson提升整整一倍。但第三方库有一定的维护成本,但数据量大的时候,使用第三方库也是值得的。