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

开发 后端
由于我们的应用程序在 Kubernetes 环境中的节点上运行,当我们的 Go 应用程序开始运行时,它可以拥有与节点中的核心数量一样多的线程。

优化您的 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. 对结构体字段进行排序

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

例如:

type testStruct struct {
 testBool1  bool    // 1 byte
 testFloat1 float64 // 8 bytes
 testBool2  bool    // 1 byte
 testFloat2 float64 // 8 bytes
}

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

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

这是因为在 64 位架构中内部内存对齐的工作方式。

many boxes showing 8 bytes of testbool1, testFIoat1, testbool2, testFIoat2

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

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 等工具来自动对结构体进行排序。

fieldalignment -fix ./... 

3. 垃圾回收调优

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

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

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

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

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

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

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

// 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

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

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

5. 使用 jsoniter 替代 encoding/json

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

Jsoniter 是 encoding/json 的 100% 兼容的替代品。

以下是一些性能基准:

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

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 来减少堆分配

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

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

以下是一个示例:

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%。

责任编辑:赵宁宁 来源: 技术的游戏
相关推荐

2016-10-25 10:12:13

2011-02-24 10:56:34

人才

2016-07-08 15:02:47

云计算

2010-10-08 11:13:22

MySQL修改密码

2023-09-06 08:00:00

ChatGPT数据分析

2022-07-11 10:07:45

云计算网络云网络

2023-11-06 08:01:09

Go同步异步

2021-12-06 06:58:50

List重复数据

2022-06-10 10:25:07

CIOIT领导者职业生涯

2022-06-09 08:46:58

ITCIO职业

2023-05-15 18:32:20

2022-05-30 16:42:20

数据中心

2022-02-21 22:47:36

首席信息官IT技术

2023-04-26 08:41:16

Git撤消更改

2023-04-03 20:29:00

Linux环境变量

2015-07-09 10:13:05

IT基础设施支出数据中心

2023-07-29 00:08:32

2023-08-02 10:05:28

2023-12-08 08:53:37

数据中心人工智能自动化

2022-10-25 12:09:13

点赞
收藏

51CTO技术栈公众号