面试题答案
一键面试defer语句对函数调用栈的性能影响
- 额外开销:当函数中包含defer语句时,在函数执行时会为defer语句创建一个defer记录(defer record),并将其压入defer栈。这个过程会带来一定的栈空间开销和压栈操作的时间开销。例如:
func testDefer() {
for i := 0; i < 10000; i++ {
defer fmt.Println(i)
}
}
在这个函数中,每次循环都要创建一个defer记录并压栈,随着循环次数增加,栈空间开销和操作时间也会增加。
- 延迟执行时机:defer语句中的函数或方法会在函数正常返回或者发生panic时才会执行,并且是按照后进先出(LIFO)的顺序执行。这意味着如果defer语句中的操作比较耗时,可能会导致函数返回延迟,影响整体性能。比如:
func longRunningDefer() {
defer func() {
time.Sleep(time.Second)
}()
// 函数主体逻辑
}
在这个例子中,函数主体执行完毕后,还要等待defer中的睡眠操作完成才能真正返回,这会增加函数的整体执行时间。
defer语句对垃圾回收的性能影响
- 延迟对象释放:如果defer语句中引用了函数内创建的对象,那么这些对象在函数正常返回或panic前不会被垃圾回收。因为defer语句中的代码持有对象的引用,导致垃圾回收器无法回收这些对象,从而可能会造成短暂的内存占用增加。例如:
func deferAndGC() {
data := make([]byte, 1024*1024) // 创建1MB的字节切片
defer func() {
// 这里对data有引用,data在defer执行完前不会被回收
fmt.Println(len(data))
}()
// 函数主体逻辑,此时data已经占用内存,但在defer执行完前不能被回收
}
- 增加垃圾回收压力:如果在循环中频繁使用defer,可能会导致大量对象延迟释放,在defer集中执行时,可能会给垃圾回收器带来较大压力,影响垃圾回收的效率。例如:
func deferInLoop() {
for i := 0; i < 10000; i++ {
data := make([]byte, 1024)
defer func() {
fmt.Println(len(data))
}()
}
}
在这个循环中,每次迭代都创建了一个新的字节切片,并通过defer引用,这些对象在循环结束后才可能被回收,可能导致垃圾回收压力增大。
优化defer使用提升性能的方法
- 减少不必要的defer:避免在循环中使用defer,除非必要。如果可以在循环内完成相关操作,尽量不使用defer。例如,将上面的
testDefer
函数优化为:
func testNoDefer() {
var results []int
for i := 0; i < 10000; i++ {
results = append(results, i)
}
for _, v := range results {
fmt.Println(v)
}
}
这样避免了循环内的defer压栈开销。
- 尽早释放资源:如果defer语句只是为了释放资源(如文件句柄等),可以考虑在资源使用完毕后尽早释放,而不是依赖defer。例如:
func readFile() {
file, err := os.Open("test.txt")
if err != nil {
return
}
defer file.Close()
// 读取文件逻辑
// 读取完成后,这里可以手动提前关闭文件,避免defer延迟关闭
file.Close()
}
这样在读取完成后立即关闭文件,减少了defer延迟关闭可能带来的资源占用时间。
- 合并defer操作:如果有多个defer操作,尽量合并为一个。例如:
func multipleDefer() {
defer func() {
// 合并多个清理操作
fmt.Println("清理操作1")
fmt.Println("清理操作2")
}()
// 函数主体逻辑
}
这样减少了defer栈的记录数量,降低栈空间开销。