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

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 过程中多次扩容。扩容会导致额外的堆分配和数据拷贝。这种优化可以减少堆分配的频率和大小,但无法完全避免切片的逃逸。
  • 减少不必要的引用:如果切片的生命周期较短,可以考虑在函数内部直接操作切片,而不是返回它。但这取决于具体需求。

🌺🌺🌺撒花!

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

在这里插入图片描述


http://www.kler.cn/a/592243.html

相关文章:

  • 植物知识分享论坛毕设
  • 【QA】CRTP在模板中有哪些用处?
  • Ollama + Open WebUI 本地部署DeepSeek
  • test_cases测试用例层/test_1_login
  • 2023 CSP-J 题解
  • 蓝桥杯练习day2:执行操作后的变化量
  • redis分布式锁实现Redisson+redlock中watch dog是如何判断当前线程是否持有锁进行续租的呢?
  • 事务隔离级别是?
  • kotlin 中的构造函数的作用
  • 黑盒问题的操作优化
  • TPAMI-2025 | 中山大学具身导航参数高效训练!NavCoT:通过解耦推理提升基于大模型的视觉语言导航
  • Python列表1
  • hexo+git pages搭建网站避坑QAQ
  • 基于BCLinux制作Apache HTTPD 2.4.63 的RPM安装包
  • JAVA-多线程join()等待一个线程
  • 精细护理:为进行性核上性麻痹患者筑牢生活防线
  • stm32第七天震动传感器
  • NLP高频面试题(四)——BN和LN的区别与联系,为什么attention要用LN
  • 五、AIGC大模型_09手动实现ReAct_Agent
  • linux /bin/bash丢失修复