在项目实际运用中发现数据量较大时,通过pprof进行性能分析发现string到byte的标准转换内存消耗十分大,后使用了unsafe包进行强转后性能有了很大的提升,在此记录下。
转换方式
标准转换
1 2 3 4 5 6
| s1 := "hello" b := []byte(s1)
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
|
由上述可见,强转换性能明显优于标准转换
原理分析
首先需要了解string
和slice
的底层数据结构:
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申请新的内存,之后再进行数据拷贝工作。而强转换只是更改指针的指向。故当数据较大时,两者性能差距越明显。