go unsafe常见应用

目录

1.强制类型转换

2.string与[]byte的高效转换

3.高效动态替换


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.goslicebytetostringtmpstringtoslicebytetmp两个函数 ,如下

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不会被修改,也是只用于编译器内部优化,目前有三种场景:

  1. 假设有一个key为string的map遍历m,你想使用[]byte类型的变量k做查找操作,比如 m[string(k)]
  2. 做字符串拼接操作时,比如 <"+string(b)+">,其中b是[]byte类型
  3. []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()
}

原创,转载请注明来自


 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值