👇我在这儿
1. 避免使用反射
反射是 Go 中一个强大的特性,它允许程序在运行时自我检查和修改自身的结构和行为。
你可以使用反射来确定一个值的类型,访问其字段,并调用其方法:
package main
import (
"fmt"
"reflect"
)
// 通过反射来获取 x 的类型
func
main
() {
x := 100
v := reflect.ValueOf(x)
t := v.Type()
fmt.Println(
"Type:"
, t)
}
但是! 反射是在运行时进行值的自我检查和操作,而不是编译时。
Go运行时必须执行额外的工作来确定反射值的类型和结构,这可能会增加开销并减慢程序速度。
反射还会使代码更难以阅读和理解而使影响生产力受到影响。
2. 避免使用字符串拼接
通常使用 bytes.Buffer
类型来构建字符串比使用 +
操作符连接字符串更有效率。
看看这个性能较差的代码:
s :=
""
for
i := 0; i < 100000; i++ {
s +=
"x"
}
fmt.Println(s)
这段代码每次循环都会创建一个新的字符串,这会使效率低下且导致性能变差。
相反地,你可以使用 bytes.Buffer
更高效地构建字符串:
// 使用 bytes.Buffer 来构建字符串
var buffer bytes.Buffer
for
i := 0; i < 100000; i++ {
buffer.WriteString(
"x"
)
}
s := buffer.String()
fmt.Println(s)
另一个解决方案是使用 strings.Builder
。
它的用法类似于 bytes.Buffer
,但提供更好的性能:
// 使用 strings.Builder 来构建字符串
var builder strings.Builder
for
i := 0; i < 100000; i++ {
builder.WriteString(
"x"
)
}
s := builder.String()
fmt.Println(s)
以下是基础测试
- 使用
bytes.Buffer
比使用字符串拼接快得多,在某些情况下性能提升超过 250 倍 - 使用
strings.Builder
大约比bytes.Buffer
快1.5倍
需要注意的是,实际的性能提升可能因为特定的 CPU 和代码运行环境等因素而有所差异。
strings.Builder比bytes.Buffer更快的原因有几个
strings.Builder
专门针对字符串的构建进行了优化。bytes.Buffer
是一个更通用的缓冲区,可以用于构建任何类型的数据,但它可能没有像 strings.Builder
那样专门优化字符串构建的性能。3. 预分配切片和 map 的空间
在Go中,为预期容纳的元素数量适当分配切片的容量可以提高性能。
这是因为分配具有更大容量的切片可以减少在添加元素时需要调整切片大小的次数。
下面是压力测试:
func
main
() {
start := time.Now()
s := make([]int, 0, 10)
for
i := 0; i < 100000; i++ {
s = append(s, i)
}
elapsed := time.Since(start)
fmt.Printf(
"Allocating slice with small capacity: %vn"
, elapsed)
start = time.Now()
s = make([]int, 0, 100000)
for
i := 0; i < 100000; i++ {
s = append(s, i)
}
elapsed = time.Since(start)
fmt.Printf(
"Allocating slice with larger capacity: %vn"
, elapsed)
}
是的,通过预分配,我们能够将速度提升3倍。
我已经在一篇关于切片的文章中对于为什么预分配更快[1]写了一个详细的解释,你可以通过文末的链接查看。
4. 避免使用只有一个具体类型的接口
如果你知道一个接口只会有一个具体类型,你可以直接使用该具体类型,以避免接口的开销。
直接使用具体类型可以比使用接口更高效,因为它避免了在接口中存储类型和值的开销。
这里有一个例子,比较了在 Go 中使用接口和直接使用具体类型的性能:
func
main
() {
start := time.Now()
var s Shape = &Circle{radius: 10}
for
i := 0; i < 100000; i++ {
s.Area()
}
elapsed := time.Since(start)
fmt.Printf(
"Using Shape interface: %sn"
, elapsed)
start = time.Now()
c := Circle{radius: 10}
for
i := 0; i < 100000; i++ {
c.Area()
}
elapsed = time.Since(start)
fmt.Printf(
"Using Circle type directly: %sn"
, elapsed)
}
使用接口的耗时为358微秒,而使用具体类型的耗时为342微秒。
需要注意的是,只有当你确信一个接口只会有一个具体类型时,才应该使用这种技术。
5. 使用 go vet
govet 工具是一种静态分析工具,它可以在不运行代码的情况下帮助你找到 Go 代码中可能存在的问题。
govet
检查您的代码以查找可能导致错误或性能问题的各种问题。它就像是一个代码质量警察,不断检查以确保你没有犯任何低级错误。
要使用 govet
,可以运行 go tool vet
命令,并将要检查的 Go 源文件的名称作为参数传递:
go tool vet main.go
你也可以在 go tool vet
命令中加入 -all
标志,以检查当前目录及其子目录中的所有 Go 源文件:
go tool vet -all
govet 可能会不断地报告不需要报告的问题。
govet
的行为。Vet 注释是特殊的注释,告诉 govet
忽略某些问题或检查其他问题。以下是一个 vet 注释的例子,告诉 govet
忽略未使用的变量:
func
main
() {
var x int
//go:noinline
_ = x
}
总结:
记得要密切关注内存分配、接口、预分配等问题。如果你想将你的 Go 代码提升到更高的水平,还有很多其他的技巧和窍门可以探索。
只要保持学习的态度,并享受编程的乐趣,就可以了!祝编程愉快!
https://medium.com/@func25/go-performance-boosters-the-top-5-tips-and-tricks-you-need-to-know-e5cf6e5bc683
本文永久链接:https://github.com/gocn/translator/blob/master/2023/w12_Go_Performance_Boosters_The_Top_5_Tips_and_Tricks_You_Need_to_Know.md
校对:Jesse
原文始发于微信公众号(GoCN):Go 性能加速器:你需要知道的 5 个诀窍和技巧
- 我的微信
- 微信扫一扫
-
- 我的微信公众号
- 微信扫一扫
-
评论