go语言中string和[]byte之间的转换

在项目实际运用中发现数据量较大时,通过pprof进行性能分析发现string到byte的标准转换内存消耗十分大,后使用了unsafe包进行强转后性能有了很大的提升,在此记录下。

转换方式

标准转换

1
2
3
4
5
6
// string to []byte
s1 := "hello"
b := []byte(s1)

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

强转换

1
2
3
4
5
6
7
func string2bytes(s string) []byte {
return *(*[]byte)(unsafe.Pointer(&s))
}

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

性能对比

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
func Benchmark_NormalString2Bytes(b *testing.B) {
x := "Test Normal String 2 Bytes With Go!"
for i := 0; i < b.N; i++ {
_ = []byte(x)
}
}

func Benchmark_Bytes2String(b *testing.B) {
x := []byte("Test Unsafe Bytes 2 String With Go!")
for i := 0; i < b.N; i++ {
_ = bytes2string(x)
}
}

func Benchmark_NormalString2Bytes(b *testing.B) {
x := "Test Normal String 2 Bytes With Go!"
for i := 0; i < b.N; i++ {
_ = []byte(x)
}
}

func Benchmark_String2Bytes(b *testing.B) {
x := "Test Normal String 2 Bytes With Go!"
for i := 0; i < b.N; i++ {
_ = string2bytes(x)
}
}

测试结果如下:

1
2
3
4
5
6
7
8
9
10
11
12
D:\goproject\study_go\study_unsafe\test_byte_string>go test -bench="." -benchmem
goos: windows
goarch: amd64
pkg: study_go/study_unsafe/test_byte_string
cpu: Intel(R) Core(TM) i7-9750H CPU @ 2.60GHz
Benchmark_NormalBytes2String-12 43473496 28.20 ns/op 48 B/op 1 allocs/op
Benchmark_Bytes2String-12 1000000000 0.2593 ns/op 0 B/op 0 allocs/op
Benchmark_NormalString2Bytes-12 31570804 35.06 ns/op 48 B/op 1 allocs/op
Benchmark_String2Bytes-12 1000000000 0.2626 ns/op 0 B/op 0 allocs/op
PASS
ok study_go/study_unsafe/test_byte_string 4.436s

由上述可见,强转换性能明显优于标准转换

原理分析

首先需要了解stringslice的底层数据结构:

1
2
3
4
5
6
7
8
9
10
type StringHeader struct {
Data uintptr
Len int
}

type SliceHeader struct {
Data uintptr
Len int
Cap int
}
  • 在go中,任何类型的指针*T都可以转化为unsafe.Pointer类型的指针,它可以存储任何变量的地址
  • unsafe.Pointer类型的指针也可以转换回普通指针,而且不必和之前的类型*T相同
  • unsafe.Pointer类型还可以转换为uintptr类型,该类型保存了指针所指向地址的数值,从而可以对地址进行数值计算。

思考总结

为什么强转换性能比标准转换好?

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

在上述测试中,当数据较大时,标准转换会有一次分配内存的操作,从而导致其性能更差,而为什么强转换不受影响?

标准转换时,当数据长度大于32个字节时,需要通过mallocgc申请新的内存,之后再进行数据拷贝工作。而强转换只是更改指针的指向。故当数据较大时,两者性能差距越明显。