如何在Go中最小化垃圾回收?

huangapple go评论103阅读模式
英文:

How to minimize the garbage collection in Go?

问题

有时候你可能想要避免/减少垃圾回收器的使用,所以我想要确定如何做到这一点。

我认为下面的方法是正确的:

  • 在函数的开头声明变量。
  • 使用数组而不是切片。

还有其他的吗?

英文:

Some times you could want to avoid/minimize the garbage collector, so I want to be sure about how to do it.

I think that the next one is correct:

  • Declare variables at the beginning of the function.
  • To use array instead of slice.

Any more?

答案1

得分: 41

为了在Go语言中减少垃圾回收,你必须减少堆分配。为了减少堆分配,你必须了解何时发生分配。

以下情况总是会导致分配(至少在Go 1的gc编译器中是这样):

  • 使用new内置函数
  • 使用make内置函数(除了一些不太可能的特殊情况)
  • 当值类型为切片、映射或带有&运算符的结构体时,使用复合字面量
  • 将大于机器字的值放入接口中。(例如,字符串、切片和某些结构体大于机器字。)
  • string[]byte[]rune之间进行转换
    • 从Go 1.3开始,编译器会特殊处理此表达式以避免分配:m[string(b)],其中m是一个映射,b是一个[]byte
  • 将非常量整数值转换为string
  • defer语句
  • go语句
  • 捕获局部变量的函数字面量

以下情况可能会导致分配,具体取决于细节:

  • 取变量的地址。注意,地址可以隐式地被取到。例如,如果a不是指针并且b方法具有指针接收器类型,则a.b()可能会取到a的地址。
  • 使用append内置函数
  • 调用可变参数函数或方法
  • 对数组进行切片
  • 向映射添加元素

这个列表意在完整,并且我对此相当有信心,但我很乐意考虑添加或更正。

如果你不确定分配发生在哪里,你可以像其他人建议的那样进行性能分析,或者查看编译器生成的汇编代码。

英文:

To minimize garbage collection in Go, you must minimize heap allocations. To minimize heap allocations, you must understand when allocations happen.

The following things always cause allocations (at least in the gc compiler as of Go 1):

  • Using the new built-in function
  • Using the make built-in function (except in a few unlikely corner cases)
  • Composite literals when the value type is a slice, map, or a struct with the & operator
  • Putting a value larger than a machine word into an interface. (For example, strings, slices, and some structs are larger than a machine word.)
  • Converting between string, []byte, and []rune
    • As of Go 1.3, the compiler special cases this expression to not allocate: m[string(b)], where m is a map and b is a []byte
  • Converting a non-constant integer value to a string
  • defer statements
  • go statements
  • Function literals that capture local variables

The following things can cause allocations, depending on the details:

  • Taking the address of a variable. Note that addresses can be taken implicitly. For example a.b() might take the address of a if a isn't a pointer and the b method has a pointer receiver type.
  • Using the append built-in function
  • Calling a variadic function or method
  • Slicing an array
  • Adding an element to a map

The list is intended to be complete and I'm reasonably confident in it, but am happy to consider additions or corrections.

If you're uncertain of where your allocations are happening, you can always profile as others suggested or look at the assembly produced by the compiler.

答案2

得分: 29

避免产生垃圾相对来说比较简单。你需要了解在哪里进行了分配,并尝试避免分配。

首先,在函数的开头声明变量是没有帮助的。编译器并不知道其中的区别。然而,人们会知道其中的区别,并且会感到烦恼。

使用数组而不是切片是可行的,但这是因为数组(除非解引用)被放在堆栈上。数组还有其他问题,比如它们在函数之间是按值(复制)传递的。堆栈上的任何内容都是“非垃圾”,因为当函数返回时会被释放。任何可能逃逸函数的指针或切片都会被放在堆上,垃圾收集器必须在某个时刻处理它们。

你能做的最好的事情就是避免分配。当你完成了不再需要的大块数据时,可以重复使用它们。这是Go博客上的性能分析教程中使用的方法。我建议你阅读一下。

除了性能分析教程中的例子之外,假设你有一个名为xs[]int类型的切片。你不断地向[]int中添加元素,直到达到某个条件,然后重置它以便重新开始。如果你执行xs = nil,你现在将切片的底层数组声明为垃圾,将在垃圾回收时被收集。下次使用时,append将重新分配xs。相反,如果你执行xs = xs[:0],你仍然在重置它,但保留了旧的数组。

在大多数情况下,试图避免创建垃圾是过早的优化。对于大多数代码来说,这并不重要。但是你可能会发现偶尔有一个函数被调用了很多次,每次运行时都会分配很多内存。或者一个循环中你重新分配而不是重用。在过度优化之前,我建议你等到看到瓶颈出现。

英文:

Avoiding garbage is relatively straight forward. You need to understand where the allocations are being made and see if you can avoid the allocation.

First, declaring variables at the beginning of a function will NOT help. The compiler does not know the difference. However, human's will know the difference and it will annoy them.

Use of an array instead of a slice will work, but that is because arrays (unless dereferenced) are put on the stack. Arrays have other issues such as the fact that they are passed by value (copied) between functions. Anything on the stack is "not garbage" since it will be freed when the function returns. Any pointer or slice that may escape the function is put on the heap which the garbage collector must deal with at some point.

The best thing you can do is avoid allocation. When you are done with large bits of data which you don't need, reuse them. This is the method used in the profiling tutorial on the Go blog. I suggest reading it.

Another example besides the one in the profiling tutorial: Lets say you have an slice of type []int named xs. You continually append to the []int until you reach a condition and then you reset it so you can start over. If you do xs = nil, you are now declaring the underlying array of the slice as garbage to be collected. Append will then reallocate xs the next time you use it. If instead you do xs = xs[:0], you are still resetting it but keeping the old array.

For the most part, trying to avoid creating garbage is premature optimization. For most of your code it does not matter. But you may find every once in a while a function which is called a great many times that allocates a lot each time it is run. Or a loop where you reallocate instead of reusing. I would wait until you see the bottle neck before going overboard.

huangapple
  • 本文由 发表于 2012年9月5日 16:33:19
  • 转载请务必保留本文链接:https://go.coder-hub.com/12277426.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定