当你的 VPS 运行着多个服务应用,但其中一个有时会占用所有的资源,以至于都无法通过 ssh 访问服务器。你转到使用 Kubernetes 集群,为所有应用程序设置限制。随后看到一些应用程序被重新启动,因为 OOM-killer 解决了内存”泄漏“问题。
当然, OOM 并不总是泄漏问题,也可能是资源超支。泄漏问题大概率是由程序错误引起的,我们今天谈论的主题是如何尽量避免这种情况。
过多的资源消耗会伤害钱包,这意味着我们需要立即采取行动。
现在让我们谈谈优化。希望你能明白为什么我们不要过早优化!
现在我们按照 Go 中的标准实体分类,来给出一些实用建议。
尽量使用第三个参数:<code style="font-size: 14px;word-wrap: break-word;padding: 2px 4px;border-radius: 4px;margin: 0 2px;background-color: rgba(27,31,35,.05);font-family: Operator Mono, Consolas, Monaco, Menlo, monospace;word-break: break-all;color: #000000;background: rgba(14, 210, 247, 0.15);"><span style="font-size: 15px;">make([]T, 0, len)</span>
make([]T, 0, len)
如果不知道元素确切的数量并且切片是短暂的,可以分配更大一点,保障切片在运行时不会增长。
不要忘记使用 copy尽量不要在复制时使用 append,例如在合并两个或多个切片时。
正确迭代一个包含许多元素或大元素的切片,使用 for 去获取单个元素。通过这种方法,将避免不必要的复制。
复用切片如果对传入的切片进行某种操作并返回已经修改的结果,我们可以返回它。这样能避免新的内存分配。
如果需要从切片中切下一小块并仅使用它,该切片的主要部分也将被保留。正确的做法是,为这小块切片使用新的副本,而将旧的切片扔给 GC。
如果拼接字符串可以在一个语句中完成,那就使用 <code style="font-size: 14px;word-wrap: break-word;padding: 2px 4px;border-radius: 4px;margin: 0 2px;background-color: rgba(27,31,35,.05);font-family: Operator Mono, Consolas, Monaco, Menlo, monospace;word-break: break-all;color: #000000;background: rgba(14, 210, 247, 0.15);"><span style="font-size: 15px;">+</span>
+ 操作符。如果需要在循环中执行此操作,使用 <span style="font-size: 15px;">string.Builder</span>
<span style="font-size: 15px;">string.Builder</span>
<span style="font-size: 15px;">Grow</span>
,并使用它的 Grow
<span style="font-size: 15px;">Builder</span>
方法预先指定
转换优化
string 和 []byte 在底层结构上非常相近,有时这两种类型之间可以通过强转换来避免内存分配。字符串驻留
可以池化字符串,从而帮助编译器只存储一次相同的字符串。避免分配<span style="font-size: 15px;">fmt</span>
我们可以使用 map(级联)而不是复合键,我们可以使用字节切片。尽量不使用
fmt
包,因为它所有的方法都用到了反射。我们理解的小结构体是不超过4个字段不超过一个机器字大小。
一些典型的拷贝场景
解引用是昂贵的,我们应该尽可能少地这样做,尤其是在循环中。同时它也失去了使用快速寄存器的能力。
这项工作由编辑器进行优化,这意味着它很便宜。
我们可以对齐结构体(根据字段的大小,以正确的顺序排列它们),以此减小结构体本身的大小。
尝试编写可供编译器内联的小函数,它会很快,甚至快过自己在函数中嵌入代码。对于热路径(hot path)尤其如此。
哪些不会内联
尝试使用小参数,因为它们的复制将被优化。尝试复制和栈增长在GC负载保持平衡。避免大量参数,让你的程序使用快速寄存器(它们的数量是有限的)。
这似乎比在函数体中声明这些变量更高效。
帮助编译器优化你的代码,保存中间结果,然后会有更多的选项来优化你的代码。
尽量不要使用 defer,或者至少不要在循环中使用它。
避免在热路径分配内存,尤其是短生命对象。制作最常见分支(if,switch)
和 slice 一样,初始化 map 时,指定其大小。
struct{} 什么都不是(不占内存),因此例如传递信号时,使用它是非常有益的。
map 只能增长,不能缩小。我们需要重置 map 时,删除其所有元素是无济于事的。
如果 map 中不包含指针,那么 GC 就不会在上面浪费宝贵的时间。字符串也使用了指针,因此应该使用字节数组而不是字符串作为键。
同样,我们不想使用指针,但我们可以使用 map 和 slice 的组合,将键存储在 map 中,将值存在 slice。这样我们就可以不受限制地更改值。
请记住,要为接口分配值时,首先需要将其复制到某处,然后将指针黏贴给它。关键是复制。事实证明,接口的装箱和拆箱的成本将近似于结构体大小的一次分配。
在某些情况下,接口的装箱和拆箱期间没有分配。例如,变量和常量的小值或布尔值、具有一个简单字段的结构体、指针(包括 map、channel、func)
与其他地方一样,尽量避免不必要的分配。例如将一个接口分配给另一个接口,而不是装箱两次。
避免在频繁调用的函数参数和返回结果中使用接口。我们不需要额外的拆装包操作。减少使用接口方法调用的频率,因为它会阻止内联。
尤其是在循环中,因为事实证明它太昂贵了。解引用是我们不想自费执行的操作。
channel 同步比其他同步原语方法慢。另外, select 中的 case 越多,我们的程序就越慢。但是,select,case 加 default 有被优化。
这也很昂贵,我们应该避免它。例如,只检查(获取)一次最大切片索引,而不是多次。最好立即尝试获得极端选项。
在整篇文章中,我们看到了一些相同的优化规则。
帮助编译器做出正确的决定,它会感谢你的。在编译时分配内存,使用中间结果,并尽量保持你的代码可读。
我再次重申,对于隐式优化,基准是强制性的。如果因为编译器在不同版本之间变化太快,昨天工作的东西明天就不能工作,反之亦然。
不要忘记使用 Go 内置的分析和跟踪工具。
以上是Go:简单的优化笔记的详细内容。更多信息请关注PHP中文网其他相关文章!