Go 高性能之 string 与 []byte 转换

概述 #

字符串字符切片 互相转换,是开发中经常用到的功能,但是你能想到,一个简单的优化,就可以提高 10 倍+ 性能吗?

[]byte 转换为 string #

普通方法 #

测试代码如下:

package performance

import (
	"testing"
)

func b2s(b []byte) string {
	return string(b)
}

func Benchmark_StringWithBytes(b *testing.B) {
	bs := []byte(`hello world`)

	for i := 0; i < b.N; i++ {
		_ = b2s(bs)
	}
}

运行测试,并将基准测试结果写入文件:

$ go test -run='^$' -bench=. -count=1 -benchtime=10000000x > slow.txt

优化版本 #

测试代码如下:

package performance

import (
	"testing"
	"unsafe"
)

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

func Benchmark_StringWithBytes(b *testing.B) {
	bs := []byte(`hello world`)

	for i := 0; i < b.N; i++ {
		_ = b2s(bs)
	}
}

运行测试,并将基准测试结果写入文件:

$ go test -run='^$' -bench=. -count=1 -benchtime=10000000x > fast.txt

使用 benchstat 比较差异 #

$ benchstat -alpha=100 fast.txt slow.txt 

# 输出如下
name                old time/op  new time/op  delta
_StringWithBytes-8  0.27ns ± 0%  3.66ns ± 0%  +1234.67%  (p=1.000 n=1+1)

从输出结果中可以看到,性能方面,优化版本方法普通方法12 倍 还多。

string 转换为 []byte #

普通方法 #

测试代码如下:

package performance

import (
	"testing"
)

func s2b(s string) []byte {
	return []byte(s)
}

func Benchmark_StringWithBytes(b *testing.B) {
	s := "hello world"

	for i := 0; i < b.N; i++ {
		_ = s2b(s)
	}
}

运行测试,并将基准测试结果写入文件:

$ go test -run='^$' -bench=. -count=1 -benchtime=10000000x > slow.txt

优化版本 #

测试代码如下:

package performance

import (
	"reflect"
	"testing"
	"unsafe"
)

func s2b(s string) (b []byte) {
	bh := (*reflect.SliceHeader)(unsafe.Pointer(&b))
	sh := (*reflect.StringHeader)(unsafe.Pointer(&s))
	bh.Data = sh.Data
	bh.Cap = sh.Len
	bh.Len = sh.Len
	return b
}

func Benchmark_StringWithBytes(b *testing.B) {
	s := "hello world"

	for i := 0; i < b.N; i++ {
		_ = s2b(s)
	}
}

运行测试,并将基准测试结果写入文件:

$ go test -run='^$' -bench=. -count=1 -benchtime=10000000x > fast.txt

使用 benchstat 比较差异 #

$ benchstat -alpha=100 fast.txt slow.txt 

# 输出如下
name                old time/op  new time/op  delta
_StringWithBytes-8  0.28ns ± 0%  4.26ns ± 0%  +1422.50%  (p=1.000 n=1+1)

从输出结果中可以看到,性能方面,优化版本方法普通方法14 倍 还多。

优化原理 #

首先,我们来看一下字符串和切片的 运行时 底层数据结构。

字符串数据结构 #

type StringHeader struct {
	Data uintptr
	Len  int
}

切片数据结构 #

type SliceHeader struct {
	Data uintptr
	Len  int
	Cap  int
}

字符串和切片互相转换

两者比较 #

通过对比,我们可以看到,切片 结构只比 字符串 结构多了一个 Cap 字段,其他字段和顺序都相同, 所以可以直接通过 指针互换 的方式来实现两者的互相转换。

小结 #

本小节主要介绍了 字符串和 []byte 互相转换的 普通方法优化方法,并进行了对应的基准测试和测试结果比较, 优化方法 后的方法可以提升高达 十几倍 的性能,其实在主流的很多框架源代码中,这两个 优化方法 的身影随处可见。 读者可以将这两个方法集成到自己的项目中,试试性能能否提升。

转载申请

本作品采用 知识共享署名 4.0 国际许可协议 进行许可,转载时请注明原文链接,图片在使用时请保留全部内容,商业转载请联系作者获得授权。

© 蛮荆 | 陕公网安备 61011302001681 号 | 陕ICP备2023004378号-1 | Rendered by Hugo