Go:簡單的優化筆記
當你的 VPS 運行著多個服務應用,但其中一個有時會佔用所有的資源,以至於都無法透過 ssh 存取伺服器。你轉到使用 Kubernetes 集群,為所有應用程式設定限制。隨後看到一些應用程式被重新啟動,因為 OOM-killer 解決了記憶體」洩漏「問題。
當然, OOM 並不總是洩漏問題,也可能是資源超支。洩漏問題大機率是由程式錯誤引起的,我們今天談論的主題是如何盡量避免這種情況。
過多的資源消耗會傷害錢包,這意味著我們需要立即採取行動。
不要過早優化
現在讓我們來談談最佳化。希望你能明白為什麼我們不要太早優化!
第一,最佳化可能是無用的工作。因為我們應該先研究整個應用程序,而你的程式碼很可能不會成為瓶頸。我們需要的是快速的結果,MVP(Minimum Viable Product,最簡可行產品),然後才會考慮它的問題。 第二,最佳化都必須有所依據。也就是說,每次優化都應該建立在基準上,我們必須證明它為我們帶來了多少利潤。 第三,最佳化也許會帶來複雜。你需要知道的是,大多數最佳化都會使程式碼的可讀性變差。你需要把握好這種平衡。
優化建議
#現在我們依照 Go 中的標準實體分類,來給一些實用建議。
1. 陣列與切片
提前為切片分配記憶體
盡量使用第三個參數:<span style="font-size: 15px;"> make([]T, 0, len)</span>
如果不知道元素確切的數量並且切片是短暫的,可以分配更大一點,保障切片在運行時不會增長。
不要忘記使用 copy
盡量不要在複製時使用 append,例如在合併兩個或多個切片時。
正確迭代
一個包含許多元素或大元素的切片,使用 for 去取得單一元素。透過這種方法,將避免不必要的複製。
複用切片
如果對傳入的切片進行某種操作並傳回已修改的結果,我們可以傳回它。這樣能避免新的記憶體分配。
不要留下不使用的切片部分
如果需要從切片中切下一小塊並僅使用它,該切片的主要部分也將被保留。正確的做法是,為這小塊切片使用新的副本,而將舊的切片丟給 GC。
2. 字串
正確拼接
如果拼接字串可以在一個語句中完成,那就使用<span style="font-size: 15px;"></span>
# 運算子。如果需要在循環中執行此操作,請使用<span style="font-size: 15px;">string.Builder</span>
#,並使用它的<span style="font-size: 15px;">Grow</span>
方法預先指定<span style="font-size: 15px;">Builder</span>
的大小,減少記憶體分配次數。
轉換最佳化
string 和 []byte 在底層結構上非常相近,有時這兩種類型之間可以透過強轉換來避免記憶體分配。
字串駐留
可以池化字串,從而幫助編譯器只儲存一次相同的字串。
避免分配
我們可以使用 map(級聯)而不是複合鍵,我們可以使用位元組切片。盡量不使用 <span style="font-size: 15px;">fmt</span>
包,因為它所有的方法都用到了反射。
3. 結構體
避免拷貝大結構體
我們理解的小結構體是不超過4個欄位不超過一個機器字大小。
一些典型的拷貝場景
#投射到interface -
通道的接收與傳送 取代map 中的元素 向切片添加元素 迭代(range)
避免透過指標存取結構體字段
解引用是昂貴的,我們應該盡可能少地這樣做,尤其是在循環中。同時它也失去了使用快速暫存器的能力。
處理小結構體
這項工作由編輯器進行最佳化,這意味著它很便宜。
使用對齊減小結構體大小
我們可以對齊結構體(根據字段的大小,以正確的順序排列它們),以此減小結構體本身的大小。
4. 函數
使用內聯函數或自己內聯它們
嘗試編寫可供編譯器內聯的小函數,它會很快,甚至快過自己在函數中嵌入程式碼。對於熱路徑(hot path)尤其如此。
哪些不會內聯
recovery #select 區塊 類型宣告 defer #goroutine for-range
合理地選擇函數參數
嘗試使用小參數,因為它們的複製將被優化。嘗試複製和堆疊增長在GC負載保持平衡。避免大量參數,讓你的程式使用快速暫存器(它們的數量是有限的)。
命名回傳值
這似乎比在函數體中宣告這些變數更有效率。
儲存中間結果
幫助編譯器最佳化你的程式碼,儲存中間結果,然後會有更多的選項來最佳化你的程式碼。
仔細地使用 defer
盡量不要使用 defer,或至少不要在循環中使用它。
協助 hot path
避免在熱路徑分配內存,尤其是短生命物件。製作最常見分支(if,switch)
5. Map
提前分配記憶體
和slice 一樣,初始化map 時,指定其大小。
使用空結構體為值
struct{} 什麼都不是(不佔記憶體),因此例如傳遞訊號時,使用它是非常有益的。
清空 map
map 只能增長,不能縮小。我們需要重置 map 時,刪除其所有元素是無濟於事的。
盡量不在鍵和值中使用指標
如果 map 不包含指針,那麼 GC 就不會在上面浪費寶貴的時間。字串也使用了指針,因此應該使用位元組數組而不是字串作為鍵。
減少修改次數
同樣,我們不想使用指針,但我們可以使用 map 和 slice 的組合,將鍵存儲在 map 中,將值存在 slice。這樣我們就可以不受限制地更改值。
6. Interface
計算記憶體分配
請記住,要為介面分配值時,首先需要將其複製到某處,然後將指針貼給它。關鍵是複製。事實證明,介面的裝箱和拆箱的成本將近似於結構體大小的一次分配。
選擇最佳類型
在某些情況下,介面的裝箱和拆箱期間沒有指派。例如,變數和常數的小值或布林值、具有一個簡單欄位的結構體、指標(包括map、channel、func)
#避免記憶體分配
<span style="font-size: 15px;"></span>
##與其他地方一樣,盡量避免不必要的分配。例如將一個接口分配給另一個接口,而不是裝箱兩次。僅在需要時使用<span style="font-size: 15px;"></span>
避免在頻繁呼叫的函數參數和傳回結果中使用介面。我們不需要額外的拆裝包操作。減少使用介面方法呼叫的頻率,因為它會阻止內聯。7. 指標、通道、邊界檢查
避免不必要的解引用<span style="font-size: 15px;"></span>
尤其是在循環中,因為事實證明它太昂貴了。解引用是我們不想自費執行的操作。 ######使用通道效率低
channel 同步比其他同步原語方法慢。另外, select 中的 case 越多,我們的程式就越慢。但是,select,case 加 default 有被最佳化。
避免不必要的邊界檢查
這也很昂貴,我們應該避免它。例如,只檢查(獲取)一次最大切片索引,而不是多次。最好立即嘗試獲得極端選項。
總結
在整篇文章中,我們看到了一些相同的最佳化規則。
幫助編譯器做出正確的決定,它會感謝你的。在編譯時分配內存,使用中間結果,並儘量保持你的程式碼可讀。
我再次重申,對於隱式最佳化,基準是強制性的。如果因為編譯器在不同版本之間變化太快,昨天工作的東西明天就不能工作,反之亦然。
不要忘記使用 Go 內建的分析和追蹤工具。
以上是Go:簡單的優化筆記的詳細內容。更多資訊請關注PHP中文網其他相關文章!

熱AI工具

Undresser.AI Undress
人工智慧驅動的應用程序,用於創建逼真的裸體照片

AI Clothes Remover
用於從照片中去除衣服的線上人工智慧工具。

Undress AI Tool
免費脫衣圖片

Clothoff.io
AI脫衣器

AI Hentai Generator
免費產生 AI 無盡。

熱門文章

熱工具

記事本++7.3.1
好用且免費的程式碼編輯器

SublimeText3漢化版
中文版,非常好用

禪工作室 13.0.1
強大的PHP整合開發環境

Dreamweaver CS6
視覺化網頁開發工具

SublimeText3 Mac版
神級程式碼編輯軟體(SublimeText3)

熱門話題

在Go中,可以使用gorilla/websocket包發送WebSocket訊息。具體步驟:建立WebSocket連線。傳送文字訊息:呼叫WriteMessage(websocket.TextMessage,[]byte("訊息"))。發送二進位訊息:呼叫WriteMessage(websocket.BinaryMessage,[]byte{1,2,3})。

在Go中,可以使用正規表示式比對時間戳記:編譯正規表示式字串,例如用於匹配ISO8601時間戳記的表達式:^\d{4}-\d{2}-\d{2}T \d{2}:\d{2}:\d{2}(\.\d+)?(Z|[+-][0-9]{2}:[0-9]{2})$ 。使用regexp.MatchString函數檢查字串是否與正規表示式相符。

在Go中,函數生命週期包括定義、載入、連結、初始化、呼叫和返回;變數作用域分為函數級和區塊級,函數內的變數在內部可見,而區塊內的變數僅在區塊內可見。

記憶體洩漏會導致Go程式記憶體不斷增加,可通過:關閉不再使用的資源,如檔案、網路連線和資料庫連線。使用弱引用防止記憶體洩漏,當物件不再被強引用時將其作為垃圾回收目標。利用go協程,協程棧記憶體會在退出時自動釋放,避免記憶體洩漏。

Go和Go語言是不同的實體,具有不同的特性。 Go(又稱Golang)以其並發性、編譯速度快、記憶體管理和跨平台優點而聞名。 Go語言的缺點包括生態系統不如其他語言豐富、文法更嚴格、缺乏動態類型。

使用IDE檢視Go函數文件:將遊標停留在函數名稱上。按下熱鍵(GoLand:Ctrl+Q;VSCode:安裝GoExtensionPack後,F1並選擇"Go:ShowDocumentation")。

在Golang中,錯誤包裝器允許你在原始錯誤上追加上下文訊息,從而創建新錯誤。這可用於統一不同程式庫或元件拋出的錯誤類型,簡化偵錯和錯誤處理。步驟如下:使用errors.Wrap函數將原有錯誤包裝成新錯誤。新錯誤包含原始錯誤的上下文資訊。使用fmt.Printf輸出包裝後的錯誤,提供更多上下文和可操作性。在處理不同類型的錯誤時,使用errors.Wrap函數統一錯誤類型。

對並發函數進行單元測試至關重要,因為這有助於確保其在並發環境中的正確行為。測試並發函數時必須考慮互斥、同步和隔離等基本原理。可以透過模擬、測試競爭條件和驗證結果等方法對並發函數進行單元測試。
