Go 調(diào)度器 可以具有與運行設(shè)備的核心數(shù)量一樣多的線程。由于我們的應(yīng)用程序在 Kubernetes 環(huán)境中的節(jié)點上運行,當(dāng)我們的 Go 應(yīng)用程序開始運行時,它可以擁有與節(jié)點中的核心數(shù)量一樣多的線程。由于許多不同的應(yīng)用程序在這些節(jié)點上運行,因此這些節(jié)點可能包含相當(dāng)多的核心。
通過使用 https://github.com/uber-go/automaxprocs,Go 調(diào)度器使用的線程數(shù)量將與您在 k8s yaml 中定義的 CPU 限制一樣多。
示例:
應(yīng)用程序 CPU 限制(在 k8s.yaml 中定義):1 核心 節(jié)點核心數(shù)量:64
通常情況下,Go 調(diào)度器會嘗試使用 64 個線程,但如果我們使用 automaxprocs,它將僅使用一個線程。
我觀察到在我實施這個方法的應(yīng)用程序中有相當(dāng)大的性能提升。約 60% 的 CPU 使用率,約 30% 的內(nèi)存使用率和約 30% 的響應(yīng)時間。
結(jié)構(gòu)體中字段的順序直接影響您的內(nèi)存使用情況。
例如:
type testStruct struct { testBool1 bool // 1 byte testFloat1 float64 // 8 bytes testBool2 bool // 1 byte testFloat2 float64 // 8 bytes}
您可能會認為這個結(jié)構(gòu)體將占用 18 字節(jié),但實際上不會。
func main() { a := testStruct{} fmt.Println(unsafe.Sizeof(a)) // 32 bytes}
這是因為在 64 位架構(gòu)中內(nèi)部內(nèi)存對齊的工作方式。
many boxes showing 8 bytes of testbool1, testFIoat1, testbool2, testFIoat2
我們?nèi)绾谓档蛢?nèi)存使用?我們可以根據(jù)內(nèi)存填充來對字段進行排序。
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 等工具來自動對結(jié)構(gòu)體進行排序。
fieldalignment -fix ./...
在 Go 1.19 之前,我們只能使用 GOGC(runtime/debug.SetGCPercent) 來配置垃圾回收周期;然而,在某些情況下,我們可能會超出內(nèi)存限制。隨著 Go 1.19 的到來,我們現(xiàn)在擁有了 GOMEMLIMIT。GOMEMLIMIT 是一個新的環(huán)境變量,允許用戶限制 Go 進程可以使用的內(nèi)存量。這個功能提供了更好的控制 Go 應(yīng)用程序內(nèi)存使用的方式,防止它們使用過多的內(nèi)存導(dǎo)致性能問題或崩潰。通過設(shè)置 GOMEMLIMIT 變量,用戶可以確保其 Go 程序在系統(tǒng)上平穩(wěn)高效地運行,而不會對系統(tǒng)造成不必要的壓力。
它并不替代 GOGC,而是與之配合使用。您還可以禁用 GOGC 百分比配置,只使用 GOMEMLIMIT 來觸發(fā)垃圾回收。
GOGC 設(shè)為 100 和內(nèi)存限制為 100MB
GOGC 設(shè)為關(guān)閉(off)并且內(nèi)存限制為 100
在減少垃圾回收的運行量方面有明顯的效果,但在應(yīng)用此設(shè)置時需要小心。如果您不了解應(yīng)用程序的極限,請不要將 GOGC=off。
在字符串與字節(jié)之間進行轉(zhuǎn)換時,我們通常會進行變量的復(fù)制。但在 Go 內(nèi)部,這兩種類型通常使用 StringHeader 和 SliceHeader 值。我們可以在這兩種類型之間進行轉(zhuǎn)換,而不進行額外的分配。
// For Go 1.20 and higherfunc 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 等庫也在其內(nèi)部使用這種結(jié)構(gòu)。
注意: 如果您的字節(jié)或字符串值可能會在后續(xù)發(fā)生更改,請不要使用此特性。
我們通常在代碼中使用 Marshal 和 Unmarshal 方法來進行序列化或反序列化。
Jsoniter 是 encoding/json 的 100% 兼容的替代品。
以下是一些性能基準(zhǔn):
將其替換為 encoding/json 非常簡單:
import "encoding/json"json.Marshal(&data)json.Unmarshal(input, &data)import jsoniter "github.com/json-iterator/go"var json = jsoniter.ConfigCompatibleWithStandardLibraryjson.Marshal(&data)json.Unmarshal(input, &data)
對象池背后的主要概念是避免重復(fù)創(chuàng)建和銷毀對象的開銷,這可能會對性能產(chǎn)生負面影響。
緩存先前分配但未使用的項目有助于減輕垃圾回收器的負擔(dān),并允許稍后重新使用它們。
以下是一個示例:
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 中的內(nèi)存泄漏問題。以前,它為每個請求創(chuàng)建一個新的 gzip writer。我創(chuàng)建了一個池,以便代理程序可以使用該池中的 writer,而不是為每個請求創(chuàng)建新的 gzip writer 實例,從而大大減少了堆使用,并因此減少了系統(tǒng)的垃圾回收次數(shù)。這個改進大約將我們應(yīng)用程序的 CPU 使用率降低了約 40%,內(nèi)存使用率降低了約 22%。
本文鏈接:http://www.www897cc.com/showinfo-26-17553-0.html提升您的 Go 應(yīng)用性能的六種方法
聲明:本網(wǎng)頁內(nèi)容旨在傳播知識,若有侵權(quán)等問題請及時與本網(wǎng)聯(lián)系,我們將在第一時間刪除處理。郵件:2376512515@qq.com
下一篇: Python:求求按規(guī)范寫我