go unsafe常见应用
Posted 文大侠
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了go unsafe常见应用相关的知识,希望对你有一定的参考价值。
目录
go 本身是类似java一样,底层运行虚拟机,整个程序处于托管状态(GC等),要想和底层打交道需要用到unsafe模块,unsafe也常用于性能提升和程序灵活处理场景,本文介绍unsafe常用的几个场景。
常见unsafe用法如下
1.强制类型转换
要求必须内存布局一致,如下
// 正确转换必须保证内存布局一致
func TestConvert(t *testing.T) {
var num int = 2
numf := *(*float64)(unsafe.Pointer(&num))
t.Logf("Convert int %v to float64 %v", num, numf)
type MyInt int
nums := []int{1, 2, 3}
mynums := *(*[]MyInt)(unsafe.Pointer(&nums))
t.Logf("Convert int array %v to myint array %v", nums, mynums)
}
结果如下, float64和int布局不一致所有不能正确转换
unsafe_test.go:15: Convert int 2 to float64 1e-323
unsafe_test.go:20: Convert int array [1 2 3] to myint array [1 2 3]
2.string与[]byte的高效转换
go的默认实现中,go和byte的转换需要通过拷贝,性能较低,如下测试
func BenchmarkByteString1(b *testing.B) {
str := "this is a string"
var bs []byte
b.ResetTimer()
for i := 0; i < b.N; i++ {
bs = []byte(str)
str = string(bs)
}
b.StopTimer()
}
结果如下
cpu: Intel(R) Core(TM) i7-8750H CPU @ 2.20GHz
BenchmarkByteString1
BenchmarkByteString1-12 25468410 42.08 ns/op
PASS
实际上,如下,因为string []byte底层结构很像,
// []byte其实就是byte类型的切片,对应的底层结构体定义如下(在runtime/slice.go文件中)
type slice struct {
array unsafe.Pointer
len int
cap int
}
// string对应的底层结构体定义如下(在runtime/string.go文件中)
type stringStruct struct {
str unsafe.Pointer
len int
}
因此,可以如下直接转换
// string转ytes
func Str2sbyte(s string) (b []byte) {
*(*string)(unsafe.Pointer(&b)) = s // 把s的地址付给b
*(*int)(unsafe.Pointer(uintptr(unsafe.Pointer(&b)) + 2*unsafe.Sizeof(&b))) = len(s) // 修改容量为长度
return
}
// []byte转string
func Sbyte2str(b []byte) string {
return *(*string)(unsafe.Pointer(&b))
}
func BenchmarkByteString2(b *testing.B) {
str := "this is a string"
var bs []byte
b.ResetTimer()
for i := 0; i < b.N; i++ {
bs = Str2sbyte(str)
str = Sbyte2str(bs)
}
b.StopTimer()
}
结果如下,大大提升
cpu: Intel(R) Core(TM) i7-8750H CPU @ 2.20GHz
BenchmarkByteString2
BenchmarkByteString2-12 1000000000 0.3190 ns/op
PASS
实际上,go本身的实现也用到这个技巧,参考runtime/string.go
中slicebytetostringtmp
和stringtoslicebytetmp
两个函数 ,如下
func slicebytetostringtmp(b []byte) string {
// Return a "string" referring to the actual []byte bytes.
// This is only for use by internal compiler optimizations
// that know that the string form will be discarded before
// the calling goroutine could possibly modify the original
// slice or synchronize with another goroutine.
// First such case is a m[string(k)] lookup where
// m is a string-keyed map and k is a []byte.
// Second such case is "<"+string(b)+">" concatenation where b is []byte.
// Third such case is string(b)=="foo" comparison where b is []byte.
if raceenabled && len(b) > 0 {
racereadrangepc(unsafe.Pointer(&b[0]),
uintptr(len(b)),
getcallerpc(unsafe.Pointer(&b)),
funcPC(slicebytetostringtmp))
}
return *(*string)(unsafe.Pointer(&b))
}
func stringtoslicebytetmp(s string) []byte {
// Return a slice referring to the actual string bytes.
// This is only for use by internal compiler optimizations
// that know that the slice won't be mutated.
// The only such case today is:
// for i, c := range []byte(str)
str := (*stringStruct)(unsafe.Pointer(&s))
ret := slice{array: unsafe.Pointer(str.str), len: str.len, cap: str.len}
return *(*[]byte)(unsafe.Pointer(&ret))
}
引用文章Go语言中[]byte和string类型相互转换时的性能分析和优化,描述如下
stringtoslicebytetmp
调用的前提是保证返回的[]byte之后不会被修改,只用于编译器内部优化,目前唯一的场景是在for loop中将string转换成[]byte做遍历操作时,比如for i, c := range []byte(str)
slicebytetostringtmp
调用的前提其实也是类似,保证返回的string在生命周期结束之前,[]byte不会被修改,也是只用于编译器内部优化,目前有三种场景:
- 假设有一个key为string的map遍历m,你想使用[]byte类型的变量k做查找操作,比如
m[string(k)]
- 做字符串拼接操作时,比如
<"+string(b)+">
,其中b是[]byte类型- []byte类型和常量字符串做比较操作,比如
string(b)=="foo"
3.高效动态替换
一个典型的场景是,一块共享的buffer,很多程序在同时读写,如何高性能的处理共享竞争问题?一个可行的方案是,写入时先写一个copy,然后原子替换老的内容,指针保持不变,如下
// 原子替换内存地址
func TestBuffer(t *testing.T) {
var buffer unsafe.Pointer
var wg sync.WaitGroup
var writeFn = func(index int) {
b := make([]int, 0)
b = append(b, index)
b = append(b, index)
b = append(b, index)
atomic.StorePointer(&buffer, unsafe.Pointer(&b))
}
var readFn = func() {
b := atomic.LoadPointer(&buffer)
data := *(*[]int)(b)
t.Log(b, data)
}
// 初始化
writeFn(0)
// 写入
for i := 0; i < 10; i++ {
wg.Add(1)
go func(index int) {
writeFn(i)
time.Sleep(time.Millisecond * 100)
wg.Done()
}(i)
}
// 读取
for i := 0; i < 3; i++ {
wg.Add(1)
go func() {
readFn()
time.Sleep(time.Millisecond * 100)
wg.Done()
}()
}
wg.Wait()
}
原创,转载请注明来自
以上是关于go unsafe常见应用的主要内容,如果未能解决你的问题,请参考以下文章
强制定义 Go 结构以将 unsafe.Pointer() 转换为 C 结构
我的C语言学习进阶之旅解决 Visual Studio 2019 报错:错误 C4996 ‘fscanf‘: This function or variable may be unsafe.(代码片段