Golang應(yīng)用程序性能優(yōu)化技巧分享
一、概述
隨著科技的進(jìn)步,人人都想要快速的應(yīng)用,用戶想要快速的交付,開(kāi)發(fā)者想要快速的性能,創(chuàng)業(yè)者想要兩者兼而有之。這就需要優(yōu)化您的應(yīng)用程序性能。您需要提高應(yīng)用程序的速度和性能,使其成為市場(chǎng)上最好的。您想要優(yōu)化 Golang 應(yīng)用程序的主要原因有兩個(gè)——資源效率和改善操作延遲。您的應(yīng)用程序的最終目標(biāo)應(yīng)該是它不需要太多資源而繼續(xù)等待操作。盡管您的 Golang 應(yīng)用程序太復(fù)雜以至于無(wú)法執(zhí)行其中一項(xiàng)任務(wù),但它必須依賴另一項(xiàng)任務(wù),這樣一來(lái),它就變成了一個(gè)死循環(huán)的依賴項(xiàng)。本文將討論一些關(guān)于提高 Golang 應(yīng)用程序性能的最佳實(shí)踐。
二、關(guān)于性能優(yōu)化的方向
延遲問(wèn)題
延遲優(yōu)化需要分析程序的瓶頸。主要目的在于優(yōu)化特定功能的延遲。當(dāng)您提高程序的資源效率時(shí),延遲會(huì)自動(dòng)改善。然而,改善延遲可能需要增加資源消耗。故此,我們需要意識(shí)到程序的瓶頸和熱點(diǎn),并同時(shí)處理這些問(wèn)題。
資源效率
您可以提高應(yīng)用程序性能以優(yōu)化資源使用。程序的某些部分比其他部分需要更多的資源,您應(yīng)該為您的應(yīng)用程序找到這樣的熱點(diǎn)。如 CPU、帶寬或內(nèi)存。
算法效率
通常程序可以采用不同的算法來(lái)執(zhí)行相同的操作,但效率往往因?yàn)楦鞣N因素不一而產(chǎn)生不一樣的效果,所以通過(guò)實(shí)施算法優(yōu)化,我們可以最大限度地提高應(yīng)用程序的性能。
三、提高 Golang 應(yīng)用程序性能的最佳實(shí)踐
1. 并行化 CPU 工作
同步需要花費(fèi)大量時(shí)間,當(dāng)您使用可用內(nèi)核并行工作時(shí),它肯定會(huì)優(yōu)化 Golang 應(yīng)用程序性能。這是線性加速應(yīng)用程序執(zhí)行的重要一步。這也屬于Golang相對(duì)于其他語(yǔ)言的天然優(yōu)勢(shì)(自帶彈藥庫(kù))。另外并行不一定最優(yōu),但能并行的場(chǎng)景,必然對(duì)我們有利,因?yàn)镚olang天生麗質(zhì)。
下面我們以計(jì)算也給目錄的文件總大小為例。分別以非并行和并行兩種方式進(jìn)行對(duì)比
//非并行版本
package main
import (
"fmt"
"io/ioutil"
"log"
"path/filepath"
"time"
)
const (
//目標(biāo)目錄
TargetPath = "G:\\go"
)
// 遞歸計(jì)算目錄下所有文件
func walkDir(path string, fileSize chan<- int64) {
fmt.Printf("\rwalk ... %s\n", path)
entries, err := ioutil.ReadDir(path)
if err != nil {
log.Fatal(err)
return
}
for _, e := range entries {
if e.IsDir() {
walkDir(filepath.Join(path, e.Name()), fileSize)
} else {
fileSize <- e.Size()
}
}
}
func main() {
//文件大小chennel
fileSize := make(chan int64)
//文件總大小
var sizeCount int64
//文件數(shù)目
var fileCount int
//計(jì)算目錄下所有文件占的大小總和
go func() {
walkDir(TargetPath, fileSize)
defer close(fileSize)
}()
t := time.Now()
for size := range fileSize {
fileCount++
sizeCount += size
}
fmt.Println("used time: " + time.Since(t).String())
fmt.Printf("total size: %.1fGB\nfile count: %d\n", float64(sizeCount)/1e9, fileCount)
}
//-------------結(jié)果---------------
used time: 41.6566073s
total size: 8.5GB
file count: 416088
//并行版本
package main
import (
"fmt"
"io/ioutil"
"log"
"path/filepath"
"sync"
"time"
)
const (
//goTest目錄
TargetPath = "G:\\go"
)
var waitGroup sync.WaitGroup
var ch = make(chan struct{}, 255)
// 遞歸計(jì)算目錄下所有文件
func walkDir(path string, fileSize chan<- int64) {
defer waitGroup.Done()
fmt.Printf("\rwalk ... %s\n", path)
ch <- struct{}{} //限制并發(fā)量
entries, err := ioutil.ReadDir(path)
<-ch
if err != nil {
log.Fatal(err)
return
}
for _, e := range entries {
if e.IsDir() {
waitGroup.Add(1)
go walkDir(filepath.Join(path, e.Name()), fileSize)
} else {
fileSize <- e.Size()
}
}
}
func main() {
//文件大小chennel
fileSize := make(chan int64)
//文件總大小
var sizeCount int64
//文件數(shù)目
var fileCount int
//計(jì)算目錄下所有文件占的大小總和
waitGroup.Add(1)
go walkDir(TargetPath, fileSize)
go func() {
defer close(fileSize)
waitGroup.Wait()
}()
t := time.Now()
for size := range fileSize {
fileCount++
sizeCount += size
}
fmt.Println("used time: " + time.Since(t).String())
fmt.Printf("total size: %.1fGB\nfile count: %d\n", float64(sizeCount)/1e9, fileCount)
}
//-------------結(jié)果---------------
used time: 7.3528287s
total size: 8.5GB
file count: 416088
很明顯,我們采用并行方式后,程序的運(yùn)行效率提升了接近6倍,所以,我們?cè)谶m當(dāng)場(chǎng)景下使用合適的方式進(jìn)行并行編程,就能為我們程序帶來(lái)意想不到的效果。當(dāng)然上面的代碼有些瑕疵,例如goroutine的創(chuàng)建不應(yīng)該手動(dòng),而應(yīng)該使用協(xié)程池等方式。
2.觀察你的超時(shí)
通常,輸入輸出操作需要更多時(shí)間,從而導(dǎo)致延遲。要克服這個(gè)問(wèn)題,您應(yīng)該避免在不知道其將消耗的時(shí)間的情況下執(zhí)行任何 I/O 任務(wù)。在為每個(gè)網(wǎng)絡(luò)請(qǐng)求設(shè)置超時(shí)之前,您應(yīng)該使用 SetDeadline、SetReadDeadline 和 SetWriteDeadline。
3. 使 I/O 操作異步
最常見(jiàn)的瓶頸是由于網(wǎng)絡(luò)事務(wù)和文件輸入/輸出執(zhí)行造成的。因此,為了優(yōu)化您的 Golang 應(yīng)用程序性能,您可以使獨(dú)立的 I/O 異步。以這種方式,此類操作并行運(yùn)行并改善下游延遲。此外,您可以使用 sync.WaitGroup 來(lái)同步多個(gè) I/O 操作。
4.減少Goroutines的使用
使用 Goroutines 非常便宜且易于使用,這讓我們覺(jué)得它幾乎是免費(fèi)的。但 goroutines 確實(shí)會(huì)占用大量?jī)?nèi)存,從而影響應(yīng)用程序性能。通常,Go 開(kāi)發(fā)人員會(huì)在不計(jì)算或不知道何時(shí)退出的情況下創(chuàng)建無(wú)限的 goroutine。因此,建議您僅在知道 goroutine 何時(shí)退出時(shí)才啟動(dòng)它。采用協(xié)程池來(lái)進(jìn)行管理,我們不再贅述如何創(chuàng)建一個(gè)協(xié)程池,其他章節(jié)中我們將重點(diǎn)介紹。
5. 使用無(wú)鎖算法
您應(yīng)該避免同步,因?yàn)樗鼤?huì)導(dǎo)致競(jìng)爭(zhēng)。為了提高延遲和效率,您應(yīng)該防止互斥。許多無(wú)鎖解決方案可用于一些常見(jiàn)的數(shù)據(jù)結(jié)構(gòu)。如果確實(shí)需要鎖,你首選的應(yīng)該為atomic,然后才為mutex。
6. 使用已編譯的正則表達(dá)式
有些程序可能會(huì)多次使用同一個(gè)正則表達(dá)式,如果在每次使用前都編譯正則表達(dá)式,應(yīng)用程序會(huì)很低效。因此,對(duì)于重復(fù)匹配,您應(yīng)該使用已編譯的正則表達(dá)式。 性能差距還是很明顯的,需要注意。
7.避免使用cgo
Go 程序可以通過(guò)使用 cgo 調(diào)用 C 庫(kù)。但是cgo函數(shù)的開(kāi)銷很大,它在運(yùn)行過(guò)程中會(huì)消耗線程,就像阻塞I/O一樣。您不應(yīng)該在緊密循環(huán)之間調(diào)用 C 代碼。為了 Golang 應(yīng)用程序的最佳性能,最好避免使用 cgo。后續(xù)的文章中我們將介紹如何優(yōu)化cgo的性能。(既然不得不用,那么將如何用好cgo)
8.不要在熱點(diǎn)分配內(nèi)存
當(dāng)您創(chuàng)建新對(duì)象時(shí),系統(tǒng)會(huì)消耗內(nèi)存和 CPU 周期,從而增加延遲。它占用GC效率,所以頻繁創(chuàng)建對(duì)象可不是個(gè)好事情,尤其是在熱點(diǎn)地區(qū)。因此,只要有可能,您應(yīng)該重用對(duì)象,考慮sync.Pool
9.用好sync.RWMutex
同步的重對(duì)象的完全鎖定,goroutines 需要等待很長(zhǎng)時(shí)間。因此在讀多寫少的場(chǎng)景中,優(yōu)先考慮sync.RWMutex
10. 在文本格式上使用二進(jìn)制 -
兩種二進(jìn)制文本格式在 PostgresSQL 中均有效。但是二進(jìn)制比文本格式快。使用二進(jìn)制形式時(shí),僅在從網(wǎng)絡(luò)字節(jié)序轉(zhuǎn)換時(shí)才需要處理。因此,對(duì)于 PostgresSQL 服務(wù)器,二進(jìn)制格式比文本格式的傳輸效率更高。
11. 利用緩沖 I/O-
訪問(wèn)單個(gè)對(duì)象需要磁盤操作,這會(huì)破壞程序的效率。使用緩沖輸入/輸出將有效地提高您的應(yīng)用程序速度,它必須讀取和寫入更大的數(shù)據(jù)塊。
12. 更喜歡使用 StringBuffer 或 StringBuilder-
使用“+”和“+=”運(yùn)算符,系統(tǒng)在每次賦值時(shí)分配一個(gè)新字符串。為了克服這種低效率,您應(yīng)該使用 StringBuffer 和 StringBuilder 來(lái)快速執(zhí)行您的程序。具體見(jiàn)我另一篇文章:再論Golang字符串拼接問(wèn)題
13. 選擇 Protocol Buffers 和 MessagePack
避免使用官方提供的Gob 和 JSON 作為序列化反序列化方案,因?yàn)樗鼈兪褂昧朔瓷?,?shí)現(xiàn)的方式相當(dāng)丑陋。我們推薦應(yīng)該使用 Protocol Buffers 和 MessagePack作為二進(jìn)制的序列化方案,當(dāng)然你非要用json序列化和反序列化,我們給出社區(qū)中給出的兩種技術(shù)方案,都對(duì)JSON的操作進(jìn)行性能上的優(yōu)化。
1.sonic
sonic 是字節(jié)跳動(dòng)開(kāi)源的一款 Golang JSON 庫(kù),基于即時(shí)編譯(Just-In-Time Compilation)與向量化編程(Single Instruction Multiple Data)技術(shù),大幅提升了 Go 程序的 JSON 編解碼性能。同時(shí)結(jié)合 lazy-load 設(shè)計(jì)思想,它也為不同業(yè)務(wù)場(chǎng)景打造了一套全面高效的 API。自 2021 年 7 月份發(fā)布以來(lái), sonic 已被抖音、今日頭條等業(yè)務(wù)采用,累計(jì)為字節(jié)跳動(dòng)節(jié)省了數(shù)十萬(wàn) CPU 核。
項(xiàng)目地址:sonic

2.go-json
go-json與其他庫(kù)相比,在編碼和解碼方面都非常快。 通過(guò)使用自動(dòng)代碼生成來(lái)提高性能或使用專用接口,它更容易實(shí)現(xiàn),但敢于堅(jiān)持與 并且是簡(jiǎn)單接口的兼容性。另外它高度兼容系統(tǒng)json庫(kù),所以你可以快速在你的項(xiàng)目中進(jìn)行替換使用。它采用對(duì)象池,避免反射等手段進(jìn)行優(yōu)化,以后的章節(jié)中我們將重點(diǎn)介紹反射的優(yōu)化技術(shù)。

14.預(yù)分配切片
當(dāng)您的需求達(dá)到其當(dāng)前容量時(shí),Golang 會(huì)自動(dòng)分配內(nèi)存。在重新分配期間,系統(tǒng)會(huì)在數(shù)據(jù)移動(dòng)到新位置時(shí)新的內(nèi)存。為了避免這種內(nèi)存浪費(fèi)和不必要的垃圾收集,您應(yīng)該盡可能預(yù)分配足夠的使用內(nèi)存。切片不是List,所以不要以為 var s =[]int={},以后直接append就好了,因?yàn)椴粩嗟臄U(kuò)容會(huì)帶來(lái)無(wú)窮的性能損害。
15. 對(duì)map能使用 int就別string
如果您的應(yīng)用程序使用map,那么您應(yīng)該在可以使用int作為key時(shí)就不要用string作為key。
四、總結(jié)
可靠性和應(yīng)用程序性能都很重要。永遠(yuǎn)記住,應(yīng)用程序性能速度是以毫不費(fèi)力、開(kāi)發(fā)時(shí)間和持續(xù)維護(hù)為代價(jià)的。如果您要構(gòu)建 Golang 應(yīng)用程序,了解上述提高 Golang 應(yīng)用程序性能的提示和技巧將有助于您進(jìn)行有效的應(yīng)用程序開(kāi)發(fā)。
以上就是Golang應(yīng)用程序性能優(yōu)化技巧分享的詳細(xì)內(nèi)容,更多關(guān)于Golang程序性能優(yōu)化的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
Go語(yǔ)言實(shí)現(xiàn)JSON解析的方法詳解
在日常項(xiàng)目中,使用Json格式進(jìn)行數(shù)據(jù)封裝是比較常見(jiàn)的操作。本文將詳細(xì)講解如何利用Go語(yǔ)言實(shí)現(xiàn)JSON的解析,感興趣的小伙伴可以學(xué)習(xí)一下2022-04-04
Golang 實(shí)現(xiàn)超大文件讀取的兩種方法
這篇文章主要介紹了Golang 實(shí)現(xiàn)超大文件讀取的兩種方法,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過(guò)來(lái)看看吧2021-04-04
詳解Go語(yǔ)言中獲取文件路徑的不同方法與應(yīng)用場(chǎng)景
在使用?Go?開(kāi)發(fā)項(xiàng)目時(shí),估計(jì)有不少人遇到過(guò)無(wú)法正確處理文件路徑的問(wèn)題,本文將嘗試從簡(jiǎn)單到復(fù)雜,詳細(xì)介紹?Go?中獲取路徑的不同方法及應(yīng)用場(chǎng)景,希望對(duì)大家有所幫助2024-02-02
B站新一代 golang規(guī)則引擎gengine基礎(chǔ)語(yǔ)法
這篇文章主要為大家介紹了B站新一代 golang規(guī)則引擎gengine基礎(chǔ)語(yǔ)法,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2023-12-12
Go語(yǔ)言實(shí)現(xiàn)猜數(shù)字小游戲
這篇文章主要為大家詳細(xì)介紹了Go語(yǔ)言實(shí)現(xiàn)猜數(shù)字小游戲,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2020-10-10
基于HLS創(chuàng)建Golang視頻流服務(wù)器的優(yōu)缺點(diǎn)
HLS 是 HTTP Live Streaming 的縮寫,是蘋果開(kāi)發(fā)的一種基于 HTTP 的自適應(yīng)比特率流媒體傳輸協(xié)議。這篇文章主要介紹了基于 HLS 創(chuàng)建 Golang 視頻流服務(wù)器,需要的朋友可以參考下2021-08-08
golang 結(jié)構(gòu)體初始化時(shí)賦值格式介紹
這篇文章主要介紹了golang 結(jié)構(gòu)體初始化時(shí)賦值格式介紹,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過(guò)來(lái)看看吧2020-12-12
Go語(yǔ)言:打造優(yōu)雅數(shù)據(jù)庫(kù)單元測(cè)試的實(shí)戰(zhàn)指南
Go語(yǔ)言數(shù)據(jù)庫(kù)單元測(cè)試入門:聚焦高效、可靠的數(shù)據(jù)庫(kù)代碼驗(yàn)證!想要確保您的Go應(yīng)用數(shù)據(jù)層堅(jiān)如磐石嗎?本指南將手把手教您如何利用Go進(jìn)行數(shù)據(jù)庫(kù)單元測(cè)試,輕松揪出隱藏的bug,打造無(wú)懈可擊的數(shù)據(jù)處理邏輯,一起來(lái)探索吧!2024-01-01

