Go:简单的优化笔记
当你的 VPS 运行着多个服务应用,但其中一个有时会占用所有的资源,以至于都无法通过 ssh 访问服务器。你转到使用 Kubernetes 集群,为所有应用程序设置限制。随后看到一些应用程序被重新启动,因为 OOM-killer 解决了内存”泄漏“问题。
当然, OOM 并不总是泄漏问题,也可能是资源超支。泄漏问题大概率是由程序错误引起的,我们今天谈论的主题是如何尽量避免这种情况。
过多的资源消耗会伤害钱包,这意味着我们需要立即采取行动。
不要过早优化
现在让我们谈谈优化。希望你能明白为什么我们不要过早优化!
第一,优化可能是无用的工作。因为我们应该先研究整个应用程序,而你的代码很可能不会成为瓶颈。我们需要的是快速的结果,MVP(Minimum Viable Product,最简可行产品),然后才会考虑它的问题。 第二,优化都必须有所依据。也就是说,每次优化都应该建立在基准上,我们必须证明它给我们带来了多少利润。 第三,优化也许会带来复杂。你需要知道的是,大多数优化会使代码的可读性变差。你需要把握好这种平衡。
优化建议
现在我们按照 Go 中的标准实体分类,来给出一些实用建议。
1. 数组与切片
提前为切片分配内存
尽量使用第三个参数:<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。
2. 字符串
正确拼接
如果拼接字符串可以在一个语句中完成,那就使用 <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
包,因为它所有的方法都用到了反射。3. 结构体
避免拷贝大结构体
我们理解的小结构体是不超过4个字段不超过一个机器字大小。
一些典型的拷贝场景
投射到 interface 通道的接收和发送 替换 map 中的元素 向切片添加元素 迭代(range)
避免通过指针访问结构体字段
解引用是昂贵的,我们应该尽可能少地这样做,尤其是在循环中。同时它也失去了使用快速寄存器的能力。
处理小结构体
这项工作由编辑器进行优化,这意味着它很便宜。
使用对齐减小结构体大小
我们可以对齐结构体(根据字段的大小,以正确的顺序排列它们),以此减小结构体本身的大小。
4. 函数
使用内联函数或自己内联它们
尝试编写可供编译器内联的小函数,它会很快,甚至快过自己在函数中嵌入代码。对于热路径(hot path)尤其如此。
哪些不会内联
recovery select 块 类型声明 defer goroutine for-range
合理地选择函数参数
尝试使用小参数,因为它们的复制将被优化。尝试复制和栈增长在GC负载保持平衡。避免大量参数,让你的程序使用快速寄存器(它们的数量是有限的)。
命名返回值
这似乎比在函数体中声明这些变量更高效。
保存中间结果
帮助编译器优化你的代码,保存中间结果,然后会有更多的选项来优化你的代码。
仔细地使用 defer
尽量不要使用 defer,或者至少不要在循环中使用它。
助力 hot path
避免在热路径分配内存,尤其是短生命对象。制作最常见分支(if,switch)
5. Map
提前分配内存
和 slice 一样,初始化 map 时,指定其大小。
使用空结构体为值
struct{} 什么都不是(不占内存),因此例如传递信号时,使用它是非常有益的。
清空 map
map 只能增长,不能缩小。我们需要重置 map 时,删除其所有元素是无济于事的。
尽量不在键和值中使用指针
如果 map 中不包含指针,那么 GC 就不会在上面浪费宝贵的时间。字符串也使用了指针,因此应该使用字节数组而不是字符串作为键。
减少修改次数
同样,我们不想使用指针,但我们可以使用 map 和 slice 的组合,将键存储在 map 中,将值存在 slice。这样我们就可以不受限制地更改值。
6. Interface
计算内存分配
请记住,要为接口分配值时,首先需要将其复制到某处,然后将指针黏贴给它。关键是复制。事实证明,接口的装箱和拆箱的成本将近似于结构体大小的一次分配。
选择最优类型
在某些情况下,接口的装箱和拆箱期间没有分配。例如,变量和常量的小值或布尔值、具有一个简单字段的结构体、指针(包括 map、channel、func)
避免内存分配
与其他地方一样,尽量避免不必要的分配。例如将一个接口分配给另一个接口,而不是装箱两次。
仅在需要时使用
避免在频繁调用的函数参数和返回结果中使用接口。我们不需要额外的拆装包操作。减少使用接口方法调用的频率,因为它会阻止内联。
7. 指针、通道、边界检查
避免不必要的解引用
尤其是在循环中,因为事实证明它太昂贵了。解引用是我们不想自费执行的操作。
使用通道效率低下
channel 同步比其他同步原语方法慢。另外, select 中的 case 越多,我们的程序就越慢。但是,select,case 加 default 有被优化。
避免不必要的边界检查
这也很昂贵,我们应该避免它。例如,只检查(获取)一次最大切片索引,而不是多次。最好立即尝试获得极端选项。
总结
在整篇文章中,我们看到了一些相同的优化规则。
帮助编译器做出正确的决定,它会感谢你的。在编译时分配内存,使用中间结果,并尽量保持你的代码可读。
我再次重申,对于隐式优化,基准是强制性的。如果因为编译器在不同版本之间变化太快,昨天工作的东西明天就不能工作,反之亦然。
不要忘记使用 Go 内置的分析和跟踪工具。
以上是Go:简单的优化笔记的详细内容。更多信息请关注PHP中文网其他相关文章!

热AI工具

Undresser.AI Undress
人工智能驱动的应用程序,用于创建逼真的裸体照片

AI Clothes Remover
用于从照片中去除衣服的在线人工智能工具。

Undress AI Tool
免费脱衣服图片

Clothoff.io
AI脱衣机

Video Face Swap
使用我们完全免费的人工智能换脸工具轻松在任何视频中换脸!

热门文章

热工具

记事本++7.3.1
好用且免费的代码编辑器

SublimeText3汉化版
中文版,非常好用

禅工作室 13.0.1
功能强大的PHP集成开发环境

Dreamweaver CS6
视觉化网页开发工具

SublimeText3 Mac版
神级代码编辑软件(SublimeText3)

在Go中,可以使用gorilla/websocket包发送WebSocket消息。具体步骤:建立WebSocket连接。发送文本消息:调用WriteMessage(websocket.TextMessage,[]byte("消息"))。发送二进制消息:调用WriteMessage(websocket.BinaryMessage,[]byte{1,2,3})。

在Go中,函数生命周期包括定义、加载、链接、初始化、调用和返回;变量作用域分为函数级和块级,函数内的变量在内部可见,而块内的变量仅在块内可见。

在Go中,可以使用正则表达式匹配时间戳:编译正则表达式字符串,例如用于匹配ISO8601时间戳的表达式:^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(\.\d+)?(Z|[+-][0-9]{2}:[0-9]{2})$。使用regexp.MatchString函数检查字符串是否与正则表达式匹配。

Go和Go语言是不同的实体,具有不同的特性。Go(又称Golang)以其并发性、编译速度快、内存管理和跨平台优点而闻名。Go语言的缺点包括生态系统不如其他语言丰富、语法更严格以及缺乏动态类型。

内存泄漏会导致Go程序内存不断增加,可通过:关闭不再使用的资源,如文件、网络连接和数据库连接。使用弱引用防止内存泄漏,当对象不再被强引用时将其作为垃圾回收目标。利用go协程,协程栈内存会在退出时自动释放,避免内存泄漏。

使用IDE查看Go函数文档:将光标悬停在函数名称上。按下热键(GoLand:Ctrl+Q;VSCode:安装GoExtensionPack后,F1并选择"Go:ShowDocumentation")。

对并发函数进行单元测试至关重要,因为这有助于确保其在并发环境中的正确行为。测试并发函数时必须考虑互斥、同步和隔离等基本原理。可以通过模拟、测试竞争条件和验证结果等方法对并发函数进行单元测试。

在Go中传递map给函数时,默认会创建副本,对副本的修改不影响原map。如果需要修改原始map,可通过指针传递。空map需小心处理,因为技术上是nil指针,传递空map给期望非空map的函数会发生错误。
