使用unsafe时,同时与协程使用简单分析性能数据

举报
码乐 发表于 2024/03/15 13:10:43 2024/03/15
【摘要】 为了保持清晰,本节专门使用多例程的方式充分利用cpu,以对比使用内置标准库unsafe包和语言层面的方式转换字符。 1 实例高效类型转换 性能对比我们实现go语言的类型转换,和 基于 unsafe的类型转换,并进行benchmark基准性能测试。string类型变量是不可变类型,而[]byte为可变类型,当转换为[]byte时,go需要从系统申请一块新内存,并将string类型变量值赋予这个...

为了保持清晰,本节专门使用多例程的方式充分利用cpu,以对比使用内置标准库unsafe包和语言层面的方式转换字符。

1 实例高效类型转换 性能对比

我们实现go语言的类型转换,和 基于 unsafe的类型转换,并进行benchmark基准性能测试。

string类型变量是不可变类型,而[]byte为可变类型,当转换为[]byte时,go需要从系统申请一块新内存,

并将string类型变量值赋予这个新的内存,通过下面基于unsafe包的实现,不需要额外的内存复制:

转换后的[]byte变量与输入参数的string类型变量共享底层存储(并不能修改返回的切片就能改变原字符串。)

而将[]byte转换为string则简单一些,因为[]byte内部时一个三元组ptr,len,cap, 在string内部为 ptr,len。

只需要通过Pointer将[]byte内部重新解释为string的内部表示。

在这里,只是把 “hello, world!” 这字符串转string换为 []byte 或 把 []byte转换为字符串 string

	var benchStr string = "hello, world!"

	func B2String(b []byte) string {

		return string(b)
	}

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

	func BenchmarkB2String(b *testing.B) {
		var sbe []byte = []byte(benchStr)
		for n := 0; n < b.N; n++ {
			B2String(sbe) 
		}
	}

	func BenchmarkS2Bytes(b *testing.B) {
		var sbe string = benchStr
		for n := 0; n < b.N; n++ {
			S2Bytes(sbe) 
		}
	}

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

	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 BenchmarkBytes2String(b *testing.B) {
		var sbe []byte = []byte(benchStr)
		for n := 0; n < b.N; n++ {
			Bytes2String(sbe) 
		}
	}
	func BenchmarkString2Bytes(b *testing.B) {
		var sbe string = benchStr
		for n := 0; n < b.N; n++ {
			String2Bytes(sbe) 
		}
	}

1.1 float64 和 uint64 转换没有太大区别

float64 到 uint64的转换,由于没有内存的重新分配和复制操作, 使用go语言函数和 unsafe包没有太多区别。

	goos: linux
	goarch: amd64
	cpu: AMD Ryzen 5 3500U with Radeon Vega Mobile Gfx
	BenchmarkFloat64bits
	BenchmarkFloat64bits-2          1000000000               0.3591 ns/op
	BenchmarkFloat64bits-2          1000000000               0.3238 ns/op
	BenchmarkFloat64bits-4          1000000000               0.3271 ns/op
	BenchmarkFloat64bits-4          1000000000               0.3104 ns/op
	BenchmarkFloat64bits-8          1000000000               0.3144 ns/op
	BenchmarkFloat64bits-8          1000000000               0.3318 ns/op
	BenchmarkGoFloat64bits
	BenchmarkGoFloat64bits-2        1000000000               0.3072 ns/op
	BenchmarkGoFloat64bits-2        1000000000               0.2978 ns/op
	BenchmarkGoFloat64bits-4        1000000000               0.3185 ns/op
	BenchmarkGoFloat64bits-4        1000000000               0.3242 ns/op
	BenchmarkGoFloat64bits-8        1000000000               0.2980 ns/op
	BenchmarkGoFloat64bits-8        1000000000               0.3265 ns/op
	BenchmarkGouint64bits
	BenchmarkGouint64bits-2         1000000000               0.3019 ns/op
	BenchmarkGouint64bits-2         1000000000               0.3115 ns/op
	BenchmarkGouint64bits-4         1000000000               0.3227 ns/op
	BenchmarkGouint64bits-4         1000000000               0.2969 ns/op
	BenchmarkGouint64bits-8         1000000000               0.3016 ns/op
	BenchmarkGouint64bits-8         1000000000               0.3229 ns/op
	PASS
	ok      command-line-arguments  6.546s

1.2 多cpu执行和分析结果

指令执行

   go test -v -count 2 -bench .  bytestr_change_bench_test.go   -cpu 2,4,8 >l.txt

在linux 执行结果如下:

	goos: linux
	goarch: amd64
	cpu: AMD Ryzen 5 3500U with Radeon Vega Mobile Gfx

使用go函数,转换字符 hello world, 从 byte 到string

	BenchmarkB2String
	BenchmarkB2String-2             241006406                4.974 ns/op
	BenchmarkB2String-2             246260128                5.265 ns/op
	BenchmarkB2String-4             204571192                5.306 ns/op
	BenchmarkB2String-4             227578519                5.266 ns/op
	BenchmarkB2String-8             224398243                5.691 ns/op
	BenchmarkB2String-8             230737006                5.544 ns/op

使用go函数 转换字符 hello world, 从 string到byte

	BenchmarkS2Bytes
	BenchmarkS2Bytes-2              240975625                5.047 ns/op
	BenchmarkS2Bytes-2              180348247                5.797 ns/op
	BenchmarkS2Bytes-4              233383180                4.889 ns/op
	BenchmarkS2Bytes-4              257518407                5.167 ns/op
	BenchmarkS2Bytes-8              245337022                6.379 ns/op
	BenchmarkS2Bytes-8              124589875                8.548 ns/op

使用usafe包转换字符 hello world, 从 byte 到string

	BenchmarkBytes2String
	BenchmarkBytes2String-2         1000000000               1.035 ns/op
	BenchmarkBytes2String-2         1000000000               1.009 ns/op
	BenchmarkBytes2String-4         1000000000               0.7254 ns/op
	BenchmarkBytes2String-4         1000000000               0.8996 ns/op
	BenchmarkBytes2String-8         1000000000               0.7063 ns/op
	BenchmarkBytes2String-8         1000000000               0.7204 ns/op

使用usafe包转换字符 hello world, 从 string到byte

	BenchmarkString2Bytes
	BenchmarkString2Bytes-2         1000000000               0.4363 ns/op
	BenchmarkString2Bytes-2         1000000000               0.3780 ns/op
	BenchmarkString2Bytes-4         1000000000               0.3678 ns/op
	BenchmarkString2Bytes-4         1000000000               0.3154 ns/op
	BenchmarkString2Bytes-8         1000000000               0.3109 ns/op
	BenchmarkString2Bytes-8         1000000000               0.3334 ns/op
	PASS
	ok      command-line-arguments  32.022s

这个例子中通过 -cpu 2,4,8 指令选项告知 go test 将每个性能基准测试函数分别在 GOMAXPROCS 等于 2,4,8的场景各运行一次。

从输出结果看,我们可以很容易看出不同被测试函数的性能随着 GOMAXPROCS增大后的性能变化。

每次执行基准测试,go test 都会启动 GOMAXPROCS 数量的新 goroutine,这些 goroutine 共同执行 b.N 次循环。

每个goroutine 尽量均衡分担循环次数。

这里输出的内存分配信息告诉我们,op(代表每次循环操作) 每一次执行 该值表示 函数在基准测试中 for 循环每次平均执行时间。

	BenchmarkString2Bytes-8         1000000000               0.3334 ns/op

也就是函数 BenchmarkString2Bytes 的例程 -8 每次for循环0.3334 ns 执行时间。

	BenchmarkS2Bytes-8              124589875                8.548 ns/op

也就是函数 BenchmarkS2Bytes 的例程 -8 每次for循环 8.548 ns 平均执行时间。

在不同平台执行速度相差不大。

但是从结果直观地看,go语言函数转换和unsafe包转换都在 ns 级别。

使用unsafe的转换效率最高快 25倍,也就是8.548 : 0.3334。

2 小结

得到的结果是,在特定场合比如需要内存分配和复制的场景,内置库可以大幅度提高转换效率。

这是建立在熟练操作和理解其过程的原理的基础上。
后面几个章节,将从更多例子和原理尝试解释它。

【版权声明】本文为华为云社区用户原创内容,转载时必须标注文章的来源(华为云社区)、文章链接、文章作者等基本信息, 否则作者和本社区有权追究责任。如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@huaweicloud.com
  • 点赞
  • 收藏
  • 关注作者

评论(0

0/1000
抱歉,系统识别当前为高风险访问,暂不支持该操作

全部回复

上滑加载中

设置昵称

在此一键设置昵称,即可参与社区互动!

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。