From e035e66817b2a06097e2e315889a810c7e351396 Mon Sep 17 00:00:00 2001 From: liusijia03 Date: Wed, 27 Sep 2023 10:18:08 +0800 Subject: [PATCH] add chinese version: w27 --- 2023/w27_Understanding_Allocations_in_Go.md | 134 ++++++++++---------- 1 file changed, 67 insertions(+), 67 deletions(-) diff --git a/2023/w27_Understanding_Allocations_in_Go.md b/2023/w27_Understanding_Allocations_in_Go.md index 38545f4..6932b62 100644 --- a/2023/w27_Understanding_Allocations_in_Go.md +++ b/2023/w27_Understanding_Allocations_in_Go.md @@ -1,4 +1,4 @@ -# Understanding Allocations in Go +# 理解Go中的内存分配 - 原文地址:https://medium.com/eureka-engineering/understanding-allocations-in-go-stack-heap-memory-9a2631b5035d - 原文作者:James Kirk @@ -7,63 +7,63 @@ - 校对:[]() *** -## Introduction +## 介绍 -Thanks to efficient in-built memory management in the Go runtime, we’re generally able to prioritise correctness and maintainability in our programs without much consideration for the details of how allocations are occurring. From time to time though, we may discover performance bottlenecks in our code, and want to look a little deeper. +由于 Go 运行时内置了高效的内存管理,我们通常能够优先考虑程序的正确性和可维护性,而无需过多考虑内存分配的细节。但有时,我们可能会发现代码中的性能瓶颈,并希望深入研究。 -Anyone who’s run a benchmark with the `-benchmem` flag will have seen the `allocs/op` stat in output like the below. In this post we’ll look at what counts as an alloc and what we can do to influence this number. +当使用 `-benchmem` 标志运行基准测试,就会在输出中看到如下所示的 `allocs/op` 统计。在这篇文章中,我们将了解什么算作分配以及我们可以采取哪些措施来影响这个数字。 ``` BenchmarkFunc-8 67836464 16.0 ns/op 8 B/op 1 allocs/op ``` -## The stack and heap we know and love +## 大家熟知的堆和栈 -To discuss the `allocs/op` stat in Go, we’re going to be interested in two areas of memory in our Go programs: _the stack_ and _the heap_. +为了讨论 Go 中的 `allocs/op` 统计,我们将对 Go 程序中的两个内存区域进行探究:堆和栈。 -In many popular programming environments _the stack_ usually refers to the call stack of a thread. A call stack is a LIFO stack data structure that stores arguments, local variables, and other data tracked as a thread executes functions. Each function call adds (pushes) a new frame to the stack, and each returning function removes (pops) from the stack. +在许多流行的编程语言中,栈通常指线程的调用栈。调用栈是一种 LIFO 的数据结构,用于存储参数、局部变量以及线程执行函数时跟踪的其他数据。每次函数调用都会向栈添加(推入)一个新帧,每次函数返回都会从栈中删除(弹出)。 -We must be able to safely free the memory of the most recent stack frame when it’s popped. We therefore can’t store anything on the stack that later needs to be referenced elsewhere. +当最近的栈帧弹出时,我们必须要确保能够安全地释放它的内存。因此,我们不能在堆栈上存储以后需要在其他地方引用的任何内容。 ![](https://miro.medium.com/v2/resize:fit:1400/1*t4_KKb6fEkINGsTwbcuk5w.png) -View of the call stack sometime after println has been called +上图为调用 `println` 后某个时间的调用栈视图。 -Since threads are managed by the OS, the amount of memory available to a thread stack is typically fixed, e.g. a default of 8MB in many Linux environments. This means we also need to be mindful of how much data ends up on the stack, particularly in the case of deeply-nested recursive functions. If the stack pointer in the diagram above passes the stack guard, the program will crash with a stack overflow error. +由于线程由操作系统管理,因此线程栈可用的内存量通常是固定的,例如,在许多 Linux 环境中默认为 8MB。这意味着我们还需要注意栈上最终有多少数据,特别是在深度嵌套递归函数的情况下。如果上图中的栈指针超出栈保护界线,程序将因栈溢出错误而崩溃。 -_The heap_ is a more complex area of memory that has no relation to the data structure of the same name. We can use the heap on demand to store data needed in our program. Memory allocated here can’t simply be freed when a function returns, and needs to be carefully managed to avoid leaks and fragmentation. The heap will generally grow many times larger than any thread stack, and the bulk of any optimization efforts will be spent investigating heap use. +_堆_ 是一个更复杂的内存区域,这与数据结构中的堆无关。我们可以按需使用堆来存储程序中需要的数据。此处分配的内存不能在函数返回时简单地释放,需要仔细管理以避免内存泄漏和碎片。堆通常会比任何线程栈大很多倍,大部分优化工作都在研究堆的使用上。 -## The Go stack and heap +## Go 中的栈和堆 -Threads managed by the OS are completely abstracted away from us by the Go runtime, and we instead work with a new abstraction: goroutines. Goroutines are conceptually very similar to threads, but they exist within user space. This means the runtime, and not the OS, sets the rules of how stacks behave. +由操作系统管理的线程在 Go 运行时被抽象了出来,使用一个新的抽象:goroutines。Goroutine 在概念上与线程非常相似,但它们存在于用户空间中。这意味着运行时(而不是操作系统)设置了堆栈行为的规则。 ![](https://miro.medium.com/v2/resize:fit:1400/1*20Pk1_PXMWm_jMfPpCMfUg.png) -Threads abstracted out of existence -Rather than having hard limits set by the OS, goroutine stacks start with a small amount of memory (currently 2KB). Before each function call is executed, a check within the function prologue is executed to verify that a stack overflow won’t occur. In the below diagram, the `convert()` function can be executed within the limits of the current stack size (without SP overshooting `stackguard0`). + +Goroutine 堆栈不再遵循操作系统设置的硬限制,而是以少量内存(当前为 2KB)开始。在执行每个函数调用之前,函数序言中会执行检查以验证不会发生堆栈溢出。在下图中, `convert()` 函数可以在当前堆栈大小的限制内执行(栈指针不会超过 `stackguard0` )。 ![](https://miro.medium.com/v2/resize:fit:1400/1*nP17BbLqFA3LpyLGPDatgg.png) -Close-up of a goroutine call stack +上图为 Goroutine 的调用堆栈特写 -If this wasn’t the case, the runtime would copy the current stack to a new larger space of contiguous memory before executing `convert()`. This means that stacks in Go are dynamically sized, and can typically keep growing as long as there’s enough memory available to feed them. +如果会超过 `stackguard0`,运行时将会在执行之前将 `convert()` 当前堆栈复制到新的更大的连续内存空间。这意味着 Go 中的堆栈是动态调整大小的,并且通常只要有足够的内存可以满足它们,就可以不断增长。 -The Go heap is again conceptually similar to the threaded model described above. All goroutines share a common heap and anything that can’t be stored on the stack will end up there. When a heap allocation occurs in a function being benchmarked, we’ll see the `allocs/ops` stat go up by one. It’s the job of the garbage collector to later free heap variables that are no longer referenced. +Go 堆在概念上再次类似于上述线程模型。所有 goroutine 共享一个公共堆,任何无法存储在栈中的内容都将最终存放在那里。当正在基准测试的函数中发生堆分配时,我们将看到`allocs/ops` 统计数加一。垃圾回收器的工作就是稍后释放掉不再引用的堆变量。 -For a detailed explanation of how memory management is handled in Go, see [A visual guide to Go Memory Allocator from scratch](https://medium.com/@ankur_anand/a-visual-guide-to-golang-memory-allocator-from-ground-up-e132258453ed). +有关 Go 中如何处理内存管理的详细说明,请参阅[Go 内存分配器可视化指南](https://medium.com/@ankur_anand/a-visual-guide-to-golang-memory-allocator-from-ground-up-e132258453ed)。 -## How do we know when a variable is allocated to the heap? +## 我们如何知道变量何时分配到堆? -This question is answered in the [official FAQ](https://golang.org/doc/faq#stack_or_heap). +这个问题在[官方FAQ](https://golang.org/doc/faq#stack_or_heap)中有解答。 -> Go compilers will allocate variables that are local to a function in that function’s stack frame. However, if the compiler cannot prove that the variable is not referenced after the function returns, then the compiler must allocate the variable on the garbage-collected heap to avoid dangling pointer errors. Also, if a local variable is very large, it might make more sense to store it on the heap rather than the stack. +> Go 编译器将在该函数的栈帧中分配该函数的本地变量。但是,如果编译器无法证明函数返回后该变量未被引用,则编译器必须在垃圾回收堆上分配该变量以避免悬空指针错误。此外,如果局部变量非常大,将其存储在堆上而不是栈上可能更有意义 > -> If a variable has its address taken, that variable is a candidate for allocation on the heap. However, a basic escape analysis recognizes some cases when such variables will not live past the return from the function and can reside on the stack. +> 如果一个变量已经分配地址,那么该变量就是在堆上分配的候选变量。然而,基本的逃逸分析可以识别某些情况,即此类变量不会在函数返回之后继续存在,就可以驻留在栈上。 -Since compiler implementations change over time, **there’s no way of knowing which variables will be allocated to the heap simply by reading Go code**. It is, however, possible to view the results of the _escape analysis_ mentioned above in output from the compiler. This can be achieved with the `gcflags` argument passed to `go build`. A full list of options can be viewed via `go tool compile -help`. +由于编译器的实现会随着时间推移而变化,**因此无法仅通过阅读 Go 代码来知道哪些变量将分配到堆中**。但是,可以在编译器的输出中查看上述逃逸分析的结果。`gcflags` 可以通过传递参数给 `go build` 来实现。可以通过 `go tool compile -help` 查看完整的选项列表。 -For escape analysis results, the `-m` option (`print optimization decisions`) can be used. Let’s test this with a simple program that creates two stack frames for functions `main1` and `stackIt`. +对于逃逸分析结果,可以使用 `-m` 选项 (`打印优化策略`)。让我们用一个简单的程序来测试这一点,该程序为函数 `main1` 和 `stackIt` 创建两个栈帧。 ``` func main1() { @@ -76,21 +76,21 @@ func stackIt() int { } ``` -Since we can can’t discuss stack behaviour if the compiler removes our function calls, the `noinline` [pragma](https://dave.cheney.net/2018/01/08/gos-hidden-pragmas) is used to prevent inlining when compiling the code. Let’s take a look at what the compiler has to say about its optimization decisions. The `-l` option is used to omit inlining decisions. +由于如果编译器删除了函数调用,我们就无法讨论栈行为,因此编译代码时会使用 `noinline` [编译指令](https://dave.cheney.net/2018/01/08/gos-hidden-pragmas)来防止内联。让我们看一下编译器对其优化决策的结果。`-l` 选项用于省略内联。 ``` $ go build -gcflags '-m -l' # github.com/Jimeux/go-samples/allocations ``` -Here we see that no decisions were made regarding escape analysis. In other words, variable `y` remained on the stack, and didn’t trigger any heap allocations. We can verify this with a benchmark. +在这里我们看到没有做出关于逃逸分析的优化策略。换句话说,变量 `y` 会保留在栈上,并且没有触发任何堆分配。我们可以通过基准来验证这一点。 ``` $ go test -bench . -benchmem BenchmarkStackIt-8 680439016 1.52 ns/op 0 B/op 0 allocs/op ``` -As expected, the `allocs/op` stat is `0`. An important observation we can make from this result is that **copying variables can allow us to keep them on the stack** and avoid allocation to the heap. Let’s verify this by modifying the program to avoid copying with use of a pointer. +正如预期的那样,`allocs/op` 统计数据为 `0`。从这个结果中我们可以得出的一个重要观察结果是,**复制变量可以让我们将它们保留在栈上并避免分配到堆**。让我们通过修改程序以避免使用指针进行复制来验证这一点。 ``` func main2() { @@ -104,7 +104,7 @@ func stackIt2() *int { } ``` -Let’s see the compiler output. +让我们看看编译器的输出。 ``` go build -gcflags '-m -l' @@ -112,14 +112,14 @@ go build -gcflags '-m -l' ./main.go:10:2: moved to heap: res ``` -The compiler tells us it moved the pointer `res` to the heap, which triggers a heap allocation as verified in the benchmark below +编译器告诉我们它将 `res` 指针移至堆,触发堆分配,如下面的基准测试所示 ``` $ go test -bench . -benchmem BenchmarkStackIt2-8 70922517 16.0 ns/op 8 B/op 1 allocs/op ``` -So does this mean pointers are guaranteed to create allocations? Let’s modify the program again to this time pass a pointer down the stack. +那么这是否意味着指针可以保证发生内存分配?让我们再次修改程序,这次将指针传递到栈中。 ``` func main3() { @@ -134,14 +134,14 @@ func stackIt3(y *int) int { } ``` -Yet running the benchmark shows nothing was allocated to the heap. +然而运行基准测试显示没有任何内容分配给堆。 ``` $ go test -bench . -benchmem BenchmarkStackIt3-8 705347884 1.62 ns/op 0 B/op 0 allocs/op ``` -The compiler output tells us this explicitly. +编译器输出明确地告诉我们这一点。 ``` $ go build -gcflags '-m -l' @@ -149,15 +149,15 @@ $ go build -gcflags '-m -l' ./main.go:10:14: y does not escape ``` -Why do we get this seeming inconsistency? `stackIt2` passes `res` _up the stack_ to `main`, where `y` will be referenced _after_ the stack frame of `stackIt2` has already been freed. The compiler is therefore able to judge that `y` must be moved to the heap to remain alive. If it didn’t do this, `y` wouldn’t exist by the time it was referenced in`main`. +为什么会出现这种看似不一致的情况?`stackIt2` 将 `res` 向上传递到 `main`,在 `stackIt2`的栈帧已被释放后 `y` 仍将被引用。因此编译器判断 `y` 必须移动到堆中才能保持活动状态。如果它不这样做,那么在 `main` 中引用时就 `y` 不会存在。 -`stackIt3`, on the other hand, passes `y` _down the stack_, and `y` isn’t referenced anywhere outside `main3`. The compiler is therefore able to judge that `y` can exist within the stack alone, and doesn’t need to be allocated to the heap. We won’t be able to produce a nil pointer in any circumstances by referencing `y`. +而 `stackIt3`,`y` 向下传递到栈中,并且 `y` 不会在 `main3` 外任何地方引用。因此编译器判断 `y` 可以单独存在于栈中,而不需要分配到堆中。在任何情况下,通过引用 `y` ,我们都不会产生 nil 指针。 -**A general rule we can infer from this is that sharing pointers up the stack results in allocations, whereas sharing points down the stack doesn’t.** However, this is not guaranteed, so you’ll still need to verify with `gcflags` or benchmarks to be sure. What we can say for sure is that any attempt to reduce `allocs/op` will involve hunting out wayward pointers. +**我们可以从中推断出一个一般规则:向上传递栈结果的共享指针会导致分配,向下传递栈的共享指针则不会。** 但是,这并不能得到保证,因此您仍然需要使用 `gcflags` 或基准进行验证才能确定。我们可以肯定地说,任何减少 `allocs/op` 的尝试都将涉及寻找野生指针。 -## Why do we care about heap allocations? +## 为什么我们关心堆分配? -We’ve learnt a little about what the `alloc` in `allocs/op` means, and how to verify if an allocation to the heap is triggered, but why should we care if this stat is non-zero in the first place? The benchmarks we’ve already done can begin to answer this question. +我们已经了解了一些关于 `allocs/op` 的含义,以及如何验证是否触发了对堆的分配,为什么我们首先应该关心这个统计数据是否非零呢?我们可以用已经完成的基准测试来回答这个问题。 ``` BenchmarkStackIt-8 680439016 1.52 ns/op 0 B/op 0 allocs/op @@ -165,23 +165,23 @@ BenchmarkStackIt2-8 70922517 16.0 ns/op 8 B/op 1 allocs/op BenchmarkStackIt3-8 705347884 1.62 ns/op 0 B/op 0 allocs/op ``` -Despite the memory requirements of the variables involved being almost equal, the relative CPU overhead of `BenchmarkStackIt2` is pronounced. We can get a little more insight by generating flame graphs of the CPU profiles of the `stackIt` and `stackIt2` implementations. +尽管所涉及的变量的分配内存没有太大差距,但 `BenchmarkStackIt2` 的 CPU 开销却很明显。我们可以通过生成和实现 `stackIt` 和 `stackIt2` 的 CPU 剖析火焰图来获得更多内容。 ![](https://miro.medium.com/v2/resize:fit:1400/1*czZGGPLuR-wsNt22Vf2PdQ.png) -stackIt CPU profile + ![](https://miro.medium.com/v2/resize:fit:1400/1*yj-4slhJ0L9lUxZG4gFxjQ.png) -stackIt2 CPU profile -`stackIt` has an unremarkable profile that runs predictably down the call stack to the `stackIt` function itself. `stackIt2`, on the other hand, is making heavy use of a large number of runtime functions that eat many additional CPU cycles. This demonstrates the complexity involved in allocating to the heap, and gives some initial insight into where those extra 10 or so nanoseconds per op are going. -## What about in the real world? +`stackIt` 剖析正如预见地,沿着调用栈运行到 `stackIt` 函数本身。而 `stackIt2` ,使用了大量运行时函数,这些函数会消耗许多额外的 CPU 周期。这展示了分配到堆所涉及的复杂性,并初步了解每个操作额外的 10 纳秒左右的时间去了哪里。 + +## 那么在现实世界中呢? -Many aspects of performance don’t become apparent without production conditions. Your single function may run efficiently in microbenchmarks, but what impact does it have on your application as it serves thousands of concurrent users? +如果没有生产条件,性能的许多方面都不会显现出来。您的单个函数可能在微基准测试中高效运行,但是当它为数千个并发用户提供服务时,它会对您的应用程序产生什么影响? -We’re not going to recreate an entire application in this post, but we will take a look at some more detailed performance diagnostics using the [trace tool](https://golang.org/cmd/trace/). Let’s begin by defining a (somewhat) big struct with nine fields. +我们不会在这篇文章中重新创建整个应用程序,但我们将使用跟踪工具来了解一些更详细的性能诊断。让我们首先定义一个具有九个字段的(有点)大的结构。 ``` type BigStruct struct { @@ -191,7 +191,7 @@ type BigStruct struct { } ``` -Now we’ll define two functions: `CreateCopy`, which copies `BigStruct` instances between stack frames, and `CreatePointer`, which shares `BigStruct` pointers up the stack, avoiding copying, but resulting in heap allocations. +现在我们将定义两个函数:`CreateCopy`函数,它在栈帧之间复制 `BigStruct` 实例;以及 `CreatePointer` 函数,它共享栈上的 `BigStruct` 指针,避免复制,但会导致堆分配。 ``` //go:noinline @@ -212,7 +212,7 @@ func CreatePointer() *BigStruct { } ``` -We can verify the explanation from above with the techniques used so far. +我们可以用目前使用的技术来验证上面的解释。 ``` $ go build -gcflags '-m -l' @@ -222,7 +222,7 @@ BenchmarkCopyIt-8 211907048 5.20 ns/op 0 B/op 0 allocs/op BenchmarkPointerIt-8 20393278 52.6 ns/op 80 B/op 1 allocs/op ``` -Here are the tests we’ll use for the `trace` tool. They each create 20,000,000 instances of `BigStruct` with their respective `Create` function. +以下是我们将用于 `trace` 工具的测试结果。我们使用具有各自 `Create` 功能来创建 20,000,000 个 `BigStruct`。 ``` const creations = 20_000_000 @@ -240,7 +240,7 @@ func TestPointerIt(t *testing.T) { } ``` -Next we’ll save the trace output for `CreateCopy` to file `copy_trace.out`, and open it with the trace tool in the browser. +接下来,我们将 `CreateCopy` 跟踪输出保存到文件 `copy_trace.out`,并使用浏览器中的工具打开。 ``` $ go test -run TestCopyIt -trace=copy_trace.out @@ -252,13 +252,13 @@ Splitting trace... Opening browser. Trace viewer is listening on http://127.0.0.1:57530 ``` -Choosing `View trace` from the menu shows us the below, which is almost as unremarkable as our flame chart for the `stackIt` function. Only two of eight potential logical cores (Procs) are utilised, and goroutine G19 spends just about the entire time running our test loop — which is what we want. +从菜单中选择 `View trace` 会向我们显示以下内容,这几乎与 `stackIt` 的火焰图一样不明显。八个逻辑核 (Procs) 中仅使用了两个,并且 goroutine G19 将绝大部分时间都花在运行循环上——这正是我们想要的。 ![](https://miro.medium.com/v2/resize:fit:1400/1*NKzN-hax5TfP3PYsLzwozg.png) -Trace for 20,000,000 CreateCopy calls +跟踪 20,000,000 个 `CreateCopy` 调用 -Let’s generate the trace data for the `CreatePointer` code. +让我们生成 `CreatePointer` 代码的跟踪数据。 ``` $ go test -run TestPointerIt -trace=pointer_trace.out @@ -270,31 +270,31 @@ Splitting trace... Opening browser. Trace viewer is listening on http://127.0.0.1:57784 ``` -You may have already noticed the test took 2.224s compared to 0.281s for `CreateCopy`, and selecting `View trace` displays something much more colourful and busy this time. All logical cores were utilised and there appears to be a lot more heap action, threads, and goroutines than last time. +您可能已经注意到,`CreatePointer` 测试花费了 2.224 秒,而 `CreateCopy` 的测试花费了 0.281 秒,并且这次选择 `View trace` 显示的内容更加丰富多彩和忙碌。所有逻辑核心都被利用,并且堆操作、线程和 goroutine 似乎比上次多得多。 ![](https://miro.medium.com/v2/resize:fit:1400/1*_DwGUNYyWcNJ_OlCi48ctA.png) -Trace for 20,000,000 `CreatePointer` calls +跟踪 20,000,000 个 `CreatePointer` 调用 -If we zoom in to a millisecond or so span of the trace, we see many goroutines performing operations related to [garbage collection](https://www.ardanlabs.com/blog/2018/12/garbage-collection-in-go-part1-semantics.html). The quote earlier from the FAQ used the phrase **_garbage-collected heap_** because it’s the job of the garbage collector to clean up anything on the heap that is no longer being referenced. +如果我们放大到毫秒左右的跟踪范围,我们会看到许多 goroutine 执行与垃圾回收相关的操作。常见问题解答前面的引用使用了**垃圾回收堆**一词,因为垃圾回收器的工作是清理堆上不再被引用的任何内容。 ![](https://miro.medium.com/v2/resize:fit:1400/1*9TJdonaURcVKcWb4WeaP1Q.png) -Close-up of GC activity in the CreatePointer trace +跟踪 `CreatePointer` 中 GC 活动的特写 -Although Go’s garbage collector is increasingly efficient, the process doesn’t come for free. We can verify visually that the test code stopped completely at times in the above trace output. This wasn’t the case for `CreateCopy`, since all of our `BigStruct` instances remained on the stack, and the GC had very little to do. +尽管 Go 的垃圾回收器越来越高效,但这个过程并不是免费的。我们可以在上面的跟踪输出中直观地验证测试代码有时会完全停止。并非都如 `CreateCopy`,因为我们所有的 `BigStruct` 实例都保留在栈上,几乎没有 GC。 -Comparing the goroutine analysis from the two sets of trace data offers more insight into this. `CreatePointer` (bottom) spent over 15% of its execution time sweeping or pausing (GC) and scheduling goroutines. +比较两组跟踪数据的 goroutine 分析可以更深入地了解这一点。`CreatePointer`(下面的图)花费了超过 15% 的执行时间来清理或暂停 (GC) 和调度 goroutine。 ![](https://miro.medium.com/v2/resize:fit:1400/1*NaxhI4aXkyLh6ez9Nvqo3A.png) -Top-level goroutine analysis for CreateCopy + ![](https://miro.medium.com/v2/resize:fit:1400/1*DR8vDjAy8RkJfHxf9XlH0g.png) -Top-level goroutine analysis for CreatePointer -A look at some of the stats available elsewhere in the trace data further illustrates the cost of heap allocation, with a stark difference in the number of goroutines generated, and almost 400 STW (stop the world) events for the `CreatePointer` test. + +查看跟踪数据中其他地方可用的一些统计数据,可以进一步说明堆分配的成本,生成的 Goroutine 数量存在明显差异,并且用于测试 `CreatePointer` 的 STW(stop the world)事件数量也接近 400 个。 ``` +------------+------+---------+ @@ -308,14 +308,14 @@ A look at some of the stats available elsewhere in the trace data further illust +------------+------+---------+ ``` -Do keep in mind though that the conditions of the `CreateCopy` test are very unrealistic in a typical program, despite the title of this section. It’s normal to see the GC using a consistent amount of CPU, and pointers are a feature of any real program. However, this together with the flame graph earlier gives us some insight into why we may want to track the `allocs/op` stat, and avoid unnecessary heap allocations when possible. +尽管在典型程序中 `CreateCopy` 测试条件非常不现实。GC 使用一致数量的 CPU 是很正常的,并且指针是任何实际程序都会使用到的一个功能。然而,这让我们深入了解为什么我们想要跟踪统计 `allocs/op` 数据,并尽可能避免不必要的堆分配。 -## Summary +## 总结 -Hopefully this post gave some insight into the differences between the stack and heap in a Go program, the meaning of the `allocs/op` stat, and some of the ways in which we can investigate memory usage. +希望这篇文章能让您深入了解 Go 程序中栈和堆之间的差异、`allocs/op` 统计的含义以及我们调查内存使用情况的一些方法。 -The correctness and maintainability of our code is usually going to take precedence over tricky techniques to reduce pointer usage and skirt GC activity. Everyone knows the line about premature optimization by now, and coding in Go is no different. +我们代码的正确性和可维护性通常优先于减少指针使用和避免 GC 活动的棘手技术。现在每个人都知道关于过早优化的说法,Go 中的编码也不例外。 -If, however, we do have strict performance requirements or otherwise identify a bottleneck in a program, the concepts and tools introduced here will hopefully be a useful starting point for making the necessary optimizations. +但是,如果我们确实有严格的性能要求或想以其他方式确定程序中的瓶颈,那么这里介绍的概念和工具将有望成为进行必要优化的有用起点。 -If you want to play around with the simple code examples in this post, check out the source and README on [GitHub](https://github.com/Jimeux/go-samples/tree/master/allocations?source=post_page-----9a2631b5035d--------------------------------). +如果您想尝试本文中的简单代码示例,请查看 [GitHub](https://github.com/Jimeux/go-samples/tree/master/allocations?source=post_page-----9a2631b5035d--------------------------------) 上的源代码和README。