提升您的 Go 應用性能的 6 種方法

  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% 的響應時間。

  1. 對結構體字段進行排序

結構體中字段的順序直接影響您的內存使用情況。

例如:

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 ./...
  1. 垃圾回收調優

在 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

  1. 使用 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([]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 等庫也在其內部使用這種結構。

注意: 如果您的字節或字符串值可能會在後續發生更改,請不要使用此特性。

  1. 使用 jsoniter 替代 encoding/json

我們通常在代碼中使用 Marshal 和 Unmarshal 方法來進行序列化或反序列化。

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

以下是一些性能基準:

chart with four columns, seven rows. first column is blank, ns/op, allocation bytes, allocation times std decode, 35510 ns/op, 1960 B/op, 99 allocs/op, easyjson decode, 8449 ns/op, 160 B/op, 4 allocs/op, jsoniter decode, 5623 ns/op, 160 B/op, 3 allocs/op, std encode, 2213 ns/op, 712 B/op, 5 allocs/op, easyjson encode, 883 ns/op, 576 B/op, 3 allocs/op, jsoniter encode, 837 ns/op, 384 B/op, 4 allocs/op

將其替換爲 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)
  1. 使用 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%。

本文由 Readfog 進行 AMP 轉碼,版權歸原作者所有。
來源https://mp.weixin.qq.com/s/FiDQF9aMUiDkbmGFHMMhaQ