目录
不要过早优化" >不要过早优化
优化建议" >优化建议
1. 数组与切片
提前为切片分配内存
不要留下不使用的切片部分
2. 字符串
正确拼接
3. 结构体
避免拷贝大结构体
避免通过指针访问结构体字段
处理小结构体
使用对齐减小结构体大小
4. 函数
使用内联函数或自己内联它们
合理地选择函数参数
命名返回值
保存中间结果
仔细地使用 defer
助力 hot path
5. Map
提前分配内存
使用空结构体为值
清空 map
尽量不在键和值中使用指针
减少修改次数
6. Interface
计算内存分配
选择最优类型
避免内存分配
仅在需要时使用
7. 指针、通道、边界检查
避免不必要的解引用
使用通道效率低下
避免不必要的边界检查
总结" >总结
首页 后端开发 Golang Go:简单的优化笔记

Go:简单的优化笔记

Jul 21, 2023 pm 01:04 PM
go

在云计算时代,我们经常创建 Serverless 应用(一种云原生开发模式,允许开发人员构建和运行应用程序,而无需管理服务器)。当我们的项目采用这种模式,那基础设施维护预算将排在首位。如果我们的服务负载很低,它实际上近乎是免费的。但是如果出现问题,你将为此付出很多!当谈到金钱时,你肯定会以某方式对它做出反应。

当你的 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> 方法预先指定

Builder
的大小,减少内存分配次数。

转换优化

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中文网其他相关文章!

本站声明
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系admin@php.cn

热AI工具

Undresser.AI Undress

Undresser.AI Undress

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

AI Clothes Remover

AI Clothes Remover

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

Undress AI Tool

Undress AI Tool

免费脱衣服图片

Clothoff.io

Clothoff.io

AI脱衣机

Video Face Swap

Video Face Swap

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

热工具

记事本++7.3.1

记事本++7.3.1

好用且免费的代码编辑器

SublimeText3汉化版

SublimeText3汉化版

中文版,非常好用

禅工作室 13.0.1

禅工作室 13.0.1

功能强大的PHP集成开发环境

Dreamweaver CS6

Dreamweaver CS6

视觉化网页开发工具

SublimeText3 Mac版

SublimeText3 Mac版

神级代码编辑软件(SublimeText3)

Go WebSocket 消息如何发送? Go WebSocket 消息如何发送? Jun 03, 2024 pm 04:53 PM

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

深入理解 Golang 函数生命周期与变量作用域 深入理解 Golang 函数生命周期与变量作用域 Apr 19, 2024 am 11:42 AM

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

如何在 Go 中使用正则表达式匹配时间戳? 如何在 Go 中使用正则表达式匹配时间戳? Jun 02, 2024 am 09:00 AM

在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函数检查字符串是否与正则表达式匹配。

Golang 与 Go 语言的区别 Golang 与 Go 语言的区别 May 31, 2024 pm 08:10 PM

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

Golang 技术性能优化中如何避免内存泄漏? Golang 技术性能优化中如何避免内存泄漏? Jun 04, 2024 pm 12:27 PM

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

如何在 IDE 中查看 Golang 函数文档? 如何在 IDE 中查看 Golang 函数文档? Apr 18, 2024 pm 03:06 PM

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

Go 并发函数的单元测试指南 Go 并发函数的单元测试指南 May 03, 2024 am 10:54 AM

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

Golang 函数接收 map 参数时的注意事项 Golang 函数接收 map 参数时的注意事项 Jun 04, 2024 am 10:31 AM

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

See all articles