提升您的 Go 应用性能的 6 种方法

优化您的 Go 应用程序

1. 如果您的应用程序在 Kubernetes 中运行,请自动设置 GOMAXPROCS 以匹配 Linux 容器的 CPU 配额

Go 调度器 可以具有与运行设备的核心数量一样多的线程。由于我们的应用程序在 Kubernetes 环境中的节点上运行,当我们的 Go 应用程序开始运行时,它可以拥有与节点中的核心数量一样多的线程。由于许多不同的应用程序在这些节点上运行,因此这些节点可能包含相当多的核心。

通过使用 https://github.com/uber-go/automaxprocs,Go 调度器使用的线程数量将与您在 k8s yaml 中定义的 CPU 限制一样多。

示例:

应用程序 CPU 限制(在 k8s.yaml 中定义):1 核心

节点核心数量:64

通常情况下,Go 调度器会尝试使用 64 个线程,但如果我们使用 automaxprocs,它将仅使用一个线程。

我观察到在我实施这个方法的应用程序中有相当大的性能提升。约 60% 的 CPU 使用率,约 30% 的内存使用率和约 30% 的响应时间。

2. 对结构体字段进行排序

结构体中字段的顺序直接影响您的内存使用情况。

例如:

go 复制代码
type testStruct struct {
 testBool1  bool    // 1 byte
 testFloat1 float64 // 8 bytes
 testBool2  bool    // 1 byte
 testFloat2 float64 // 8 bytes
}

您可能会认为这个结构体将占用 18 字节,但实际上不会。

go 复制代码
func main() {
 a := testStruct{}
 fmt.Println(unsafe.Sizeof(a)) // 32 bytes
}

这是因为在 64 位架构中内部内存对齐的工作方式。有关更多信息,您可以阅读这篇文章

我们如何降低内存使用?我们可以根据内存填充来对字段进行排序。

go 复制代码
type testStruct struct {
 testFloat1 float64 // 8 bytes
 testFloat2 float64 // 8 bytes
 testBool1  bool    // 1 byte
 testBool2  bool    // 1 byte
}

func main() {
 a := testStruct{}
 fmt.Println(unsafe.Sizeof(a)) // 24 bytes
}

我们并不总是需要手动排序这些字段。您可以使用诸如 fieldalignment 等工具来自动对结构体进行排序。

bash 复制代码
fieldalignment -fix ./... 

3. 垃圾回收调优

在 Go 1.19 之前,我们只能使用 GOGC(runtime/debug.SetGCPercent) 来配置垃圾回收周期;然而,在某些情况下,我们可能会超出内存限制。随着 Go 1.19 的到来,我们现在拥有了 GOMEMLIMITGOMEMLIMIT 是一个新的环境变量,允许用户限制 Go 进程可以使用的内存量。这个功能提供了更好的控制 Go 应用程序内存使用的方式,防止它们使用过多的内存导致性能问题或崩溃。通过设置 GOMEMLIMIT 变量,用户可以确保其 Go 程序在系统上平稳高效地运行,而不会对系统造成不必要的压力。

它并不替代 GOGC,而是与之配合使用。您还可以禁用 GOGC 百分比配置,只使用 GOMEMLIMIT 来触发垃圾回收。

GOGC 设为 100 和内存限制为 100MB

GOGC 设为关闭(off)并且内存限制为 100。

在减少垃圾回收的运行量方面有明显的效果,但在应用此设置时需要小心。如果您不了解应用程序的极限,请不要将 GOGC=off

4. 使用 unsafe 包进行字符串 <-> 字节转换而不进行复制

在字符串与字节之间进行转换时,我们通常会进行变量的复制。但在 Go 内部,这两种类型通常使用 StringHeaderSliceHeader 值。我们可以在这两种类型之间进行转换,而不进行额外的分配。

go 复制代码
// For Go 1.20 and higher
func StringToBytes(s string) []byte {
 return unsafe.Slice(unsafe.StringData(s), len(s))
}

func BytesToString(b []byte) string {
 return unsafe.String(unsafe.SliceData(b), len(b))
}

// For lower versions
// Check the example here
// https://github.com/bcmills/unsafeslice/blob/master/unsafeslice.go#L116

诸如 fasthttpfiber 等库也在其内部使用这种结构。

注意: 如果您的字节或字符串值可能会在后续发生更改,请不要使用此特性。

5. 使用 jsoniter 替代 encoding/json

我们通常在代码中使用 MarshalUnmarshal 方法来进行序列化或反序列化。

Jsoniterencoding/json 的 100% 兼容的替代品。

以下是一些性能基准:

将其替换为 encoding/json 非常简单:

go 复制代码
import "encoding/json"

json.Marshal(&data)
json.Unmarshal(input, &data)
import jsoniter "github.com/json-iterator/go"

var json = jsoniter.ConfigCompatibleWithStandardLibrary
json.Marshal(&data)
json.Unmarshal(input, &data)

6. 使用 sync.Pool 来减少堆分配

对象池背后的主要概念是避免重复创建和销毁对象的开销,这可能会对性能产生负面影响。

缓存先前分配但未使用的项目有助于减轻垃圾回收器的负担,并允许稍后重新使用它们。

以下是一个示例:

go 复制代码
type Person struct {
 Name string
}

var pool = sync.Pool{
 New: func() any {
  fmt.Println("Creating a new instance")
  return &Person{}
 },
}

func main() {
 person := pool.Get().(*Person)
 fmt.Println("Get object from sync.Pool for the first time:", person)
 person.Name = "Mehmet"

 fmt.Println("Put the object back in the pool")
 pool.Put(person)

 fmt.Println("Get object from pool again:", pool.Get().(*Person))

 fmt.Println("Get object from pool again (new one will be created):", pool.Get().(*Person))
}

//Creating a new instance
//Get object from sync.Pool for the first time: &{}
//Put the object back in the pool
//Get object from pool again: &{Mehmet}
//Creating a new instance
//Get object from pool again (new one will be created): &{}

通过使用 sync.Pool,我解决了 New Relic Go Agent 中的内存泄漏问题。以前,它为每个请求创建一个新的 gzip writer。我创建了一个池,以便代理程序可以使用该池中的 writer,而不是为每个请求创建新的 gzip writer 实例,从而大大减少了堆使用,并因此减少了系统的垃圾回收次数。这个改进大约将我们应用程序的 CPU 使用率降低了约 40%,内存使用率降低了约 22%。

希望对您有所帮助,欢迎提供任何反馈。谢谢。

相关推荐
芊寻(嵌入式)15 分钟前
C转C++学习笔记--基础知识摘录总结
开发语言·c++·笔记·学习
AskHarries18 分钟前
Java字节码增强库ByteBuddy
java·后端
一颗松鼠24 分钟前
JavaScript 闭包是什么?简单到看完就理解!
开发语言·前端·javascript·ecmascript
有梦想的咸鱼_25 分钟前
go实现并发安全hashtable 拉链法
开发语言·golang·哈希算法
海阔天空_201331 分钟前
Python pyautogui库:自动化操作的强大工具
运维·开发语言·python·青少年编程·自动化
佳佳_33 分钟前
Spring Boot 应用启动时打印配置类信息
spring boot·后端
天下皆白_唯我独黑38 分钟前
php 使用qrcode制作二维码图片
开发语言·php
夜雨翦春韭42 分钟前
Java中的动态代理
java·开发语言·aop·动态代理
小远yyds44 分钟前
前端Web用户 token 持久化
开发语言·前端·javascript·vue.js
何曾参静谧1 小时前
「C/C++」C/C++ 之 变量作用域详解
c语言·开发语言·c++