• golang 内存逃逸和uintptr,unsafe.Pointer


    上半部分参考 https://www.jianshu.com/p/63404461e520

    golang 在栈或者堆中分配内存,更倾向在栈中分配因为代价低,

    内存逃逸上指,编译器编译时检查变量,发现整个生命周期是否在运行时完全可知。

    如果可知,它就可以在栈上分配。否则就说它 逃逸 了,必须在堆上分配。

    可以使用命令

    go build -gcflags=-m
    

      进行逃逸分析

    通常栈逃逸到堆的情况

    • 发送指针或带有指针的值到 channel 中。在编译时,是没有办法知道哪个 goroutine 会在 channel 上接收数据。所以编译器没法知道变量什么时候才会被释放。

    • 在一个切片上存储指针或带指针的值。一个典型的例子就是 []*string。这会导致切片的内容逃逸。尽管其后面的数组可能是在栈上分配的,但其引用的值一定是在堆上。

    • slice 的背后数组被重新分配了,因为 append 时可能会超出其容量(cap)。slice 初始化的地方在编译时是可以知道的,它最开始会在栈上分配。如果切片背后的存储要基于运行时的数据进行扩充,就会在堆上分配。

    • 在 interface 类型上调用方法。在 interface 类型上调用方法都是动态调度的 —— 方法的真正实现只能在运行时知道。想像一个 io.Reader 类型的变量 r, 调用 r.Read(b) 会使得 r 的值和切片 b 的背后存储都逃逸掉,所以会在堆上分配。

    也可以主动避免内存逃逸,使用 noescape()函数,函数源码如下
     func noescape(p unsafe.Pointer) unsafe.Pointer {
         x := uintptr(p)
         return unsafe.Pointer(x ^ 0)
    }
    

      

    • noescape() 函数的作用是遮蔽输入和输出的依赖关系。使编译器不认为 p 会通过 x 逃逸, 因为 uintptr() 产生的引用是编译器无法理解的。

    • 内置的 uintptr 类型是一个真正的指针类型,但是在编译器层面,它只是一个存储一个 指针地址 的 int 类型。代码的最后一行返回 unsafe.Pointer 也是一个 int

    • noescape() 在 runtime 包中使用 unsafe.Pointer 的地方被大量使用。如果作者清楚被 unsafe.Pointer 引用的数据肯定不会被逃逸,但编译器却不知道的情况下,这是很有用的。

    以上原文都有,然后主要说说 uintptr 和 unsafe.Pointer

    • unsafe.Pointer只是单纯的通用指针类型,用于转换不同类型指针,它不可以参与指针运算;
    • 而uintptr是用于指针运算的,GC 不把 uintptr 当指针,它只是一个存储一个 指针地址 的 int 类型,也就是说 uintptr 无法持有对象, uintptr 类型的目标会被回收;
    • unsafe.Pointer 可以和 普通指针 进行相互转换;
    • unsafe.Pointer 可以和 uintptr 进行相互转换。

    可以使用此指针方式给结构体赋值

    type Test struct {
    	a int
    	b int
    }
    
    func main()  {
    
            var test *Test = new(Test)
    	fmt.Println(test)
    
    	a := unsafe.Pointer(uintptr(unsafe.Pointer(test))+unsafe.Offsetof(test.a))
    	*((*int)(a)) = 21
    	fmt.Println(test)
    
    
    }    
    

      运行结果

    &{0 0}
    &{21 0}

     uintptr(unsafe.Pointer(test)) 获取了 test 的指针起始值,然后offsetof获取偏移量,a为结构体内a的指针,是一个通用指针

    转换为int指针,再使用* 解引用,赋值

  • 相关阅读:
    jvm垃圾收集算法
    RabbitMQ的6种工作模式详解及应用场景
    RabbitMQ 高级特性
    Linux回环IP地址lo的作用
    Mat中step的含义
    Python使用RSA加解密分段2048位
    golang发送SMTP邮件
    python2.7安装pip以及配置镜像
    golang代码静态扫描工具
    如何优雅构建一个很多参数的java对象
  • 原文地址:https://www.cnblogs.com/9527s/p/13685183.html
Copyright © 2020-2023  润新知