当前位置: 首页 > news >正文

Golang | 每日一练 (6)

💢欢迎来到张胤尘的技术站
💥技术如江河,汇聚众志成。代码似星辰,照亮行征程。开源精神长,传承永不忘。携手共前行,未来更辉煌💥

文章目录

  • Golang | 每日一练 (6)
    • 题目
    • 参考答案
      • 什么是内存逃逸?
      • 内存逃逸对程序有什么样的影响?
      • 如何避免?

Golang | 每日一练 (6)

题目

什么是内存逃逸?内存逃逸对程序有什么样的影响?如何避免?

参考答案

什么是内存逃逸?

内存逃逸是指在函数内部创建的变量或对象,在函数结束后仍然被其他部分引用或持有,从而脱离了函数的作用域。在 golang 中,编译器会根据逃逸分析的结果,将变量分配到栈或堆上。如果变量的生命周期超出了函数的作用域,就会被分配到堆上,这种现象被称为内存逃逸。

例如:

package main

import (
	"fmt"
)

func createSlice() []int {
	var s []int
	for i := 0; i < 10; i++ {
		s = append(s, i)
	}
	return s
}

func main() {
	slice := createSlice()
	fmt.Println(slice)
}

在上述代码中,在 createSlice 函数的内部创建了一个局部变量 s,并在循环中向其中添加了元素。最后,函数返回了这个切片。在正常情况下,这个局部变量 s 会被分配到函数栈上,但是逃逸分析会检测到切片 s 的底层数组需要在函数外被访问,因此会将底层数组分配到堆上,而不是栈上。

下面通过 go build 命令可以更加直观的看出内存逃逸的表现。如下所示:

$ go build -gcflags="-m" test.go 
# command-line-arguments
./test.go:7:6: can inline createSlice
./test.go:16:22: inlining call to createSlice
./test.go:17:13: inlining call to fmt.Println
./test.go:17:13: ... argument does not escape
./test.go:17:14: slice escapes to heap

在以上的输出结果中,slice escapes to heap 这是关键信息,表明 createSlice 函数返回的切片 slice 被分配到了堆上。

内存逃逸对程序有什么样的影响?

内存逃逸对程序的影响主要体现在以下几个方面:

  • 堆分配的持久性:当变量逃逸到堆上时,它们的生命周期不再受局部作用域的限制,而是由垃圾回收器管理。这意味着这些变量在使用完毕后不会立即释放,而是等待 gc 的回收。因此,程序的堆内存占用会增加。
  • 内存碎片化:频繁的堆分配和释放可能导致内存碎片化,降低内存的利用率。尤其是在长时间运行的程序中,内存碎片化可能导致可用内存减少,影响程序的性能。
  • 堆分配的开销:堆分配比栈分配更复杂且耗时。栈的分配和访问只需要通过移动栈顶指针即可;而堆分配需要动态管理内存,可能会涉及复杂的内存分配算法和同步机制。
  • 垃圾回收的负担:堆上的内存需要由 gc 管理。当堆内存增加时,gc 的运行频率也会增加,这会导致程序的 STW 或额外的性能开销。频繁的 gc 可能会显著降低程序的响应速度和吞吐量。
  • 引用计数问题:堆上的对象可能被多个引用持有,这使得生命周期管理变得复杂。例如,一个对象可能被意外地保留,导致内存泄漏。
  • 悬挂指针风险:如果堆上的对象被释放,但仍有指针指向它,可能会导致悬空指针问题,进而引发程序崩溃或未定义行为。

悬空指针是指指针曾经指向一个有效的内存位置,但该内存已被释放或回收,导致指针变得无效。尽管指针仍然保存着原来的地址,但访问该地址会产生未定义行为,因为该地址可能已经被分配给其他对象或成为不可访问的区域。

总的来说,内存逃逸对程序的影响是多方面的,既有积极的一面,也有消极的一面。在实际开发中,合理管理内存逃逸是优化程序性能和稳定性的关键。以下是一些优化建议:

  • 减少不必要的堆分配:尽量使用栈分配(如局部变量)或预分配内存,减少堆分配的频率。
  • 优化数据结构:选择合适的数据结构,避免不必要的内存逃逸。
  • 使用工具分析:利用 golang 的逃逸分析工具和性能分析工具,找出并优化不必要的内存逃逸。
  • 合理使用堆分配:在需要动态内存或跨作用域共享时,合理使用堆分配,避免过度优化。

如何避免?

在之前的代码中,由于这个场景中切片的逃逸是不可避免的(因为需要返回切片),但可以通过以下方式减少不必要的堆分配:

package main

import (
	"fmt"
)

func createSlice() []int {
	// 预分配足够的容量,避免多次扩容
	s := make([]int, 10)
	for i := 0; i < 10; i++ {
		s[i] = i
	}
	return s
}

func main() {
	slice := createSlice()
	fmt.Println(slice)
}
  • 预分配容量:使用 make([]int, 10) 预分配切片的容量,避免在 append 过程中多次扩容。扩容会导致额外的堆分配和数据拷贝。这种优化可以减少堆分配的频率和大小,但无法完全避免切片的逃逸。
  • 减少不必要的引用:如果切片的生命周期较短,可以考虑在函数内部直接操作切片,而不是返回它。但这取决于具体需求。

🌺🌺🌺撒花!

如果本文对你有帮助,就点关注或者留个👍
如果您有任何技术问题或者需要更多其他的内容,请随时向我提问。

在这里插入图片描述

相关文章:

  • Mysql 安装指南(小白入门)
  • 基于FPGA轨道交通6U机箱CPCI脉冲板板卡
  • vs2017版本与arcgis10.1的ArcObject SDK for .NET兼容配置终结解决方案
  • 【笔记】计算机网络——数据链路层
  • 10.PE导出表
  • 3.8 Spring Boot监控:Actuator+Prometheus+Grafana可视化
  • 【vue2 + Cesium】使用Cesium、添加第三方地图、去掉商标、Cesium基础配置、地图放大缩小事件、获取可视区域、层级、高度
  • Python第六章01:列表(lsit)定义语法
  • ESP32(3)UDP通信
  • 【Linux篇】:进程抢占式调度的量子纠缠--状态,优先级与上下文切换的三角博弈
  • python基础8 单元测试
  • 【算法】一维差分
  • 【Linux】Makefile秘籍
  • 深度解读 | AI驱动下的新型金融对冲策略:稀疏奖励强化学习的应用
  • 1.angular介绍
  • 第九步:web-js
  • Go基础语法阶段核心内容(5天)
  • ESP32(4)TCP通信
  • 免费实用工具,wps/office/永中通吃!
  • Matlab 高效编程:用矩阵运算替代循环
  • 北京银行一季度净赚超76亿降逾2%,不良贷款率微降
  • 中纪报:五一节前公开通报释放强烈信号,以铁律狠刹歪风邪气
  • 一张老照片里蕴含的上海文脉
  • 商务部:将积极会同相关部门加快推进离境退税政策落实落地
  • 马上评|演唱会云集,上海如何把“流量”变“留量”
  • 中国天主教组织发唁电对教皇去世表示哀悼