1. Go:簡單的優(yōu)化筆記

        共 3232字,需瀏覽 7分鐘

         ·

        2022-07-06 12:45

        點擊上方“Go語言進階學習”,進行關注

        回復“Go語言”即可獲贈從入門到進階共10本電子書

        同是天涯淪落人,相逢何必曾相識!

        本文整理譯自 Golang: simple optimization notes:https://medium.com/scum-gazeta/golang-simple-optimization-notes-70bc64673980


        在云計算時代,我們經(jīng)常創(chuàng)建 Serverless 應用(一種云原生開發(fā)模式,允許開發(fā)人員構建和運行應用程序,而無需管理服務器)。當我們的項目采用這種模式,那基礎設施維護預算將排在首位。如果我們的服務負載很低,它實際上近乎是免費的。但是如果出現(xiàn)問題,你將為此付出很多!當談到金錢時,你肯定會以某方式對它做出反應。

        當你的 VPS 運行著多個服務應用,但其中一個有時會占用所有的資源,以至于都無法通過 ssh 訪問服務器。你轉到使用 Kubernetes 集群,為所有應用程序設置限制。隨后看到一些應用程序被重新啟動,因為 OOM-killer 解決了內(nèi)存”泄漏“問題。

        當然, OOM 并不總是泄漏問題,也可能是資源超支。泄漏問題大概率是由程序錯誤引起的,我們今天談論的主題是如何盡量避免這種情況。

        過多的資源消耗會傷害錢包,這意味著我們需要立即采取行動。

        不要過早優(yōu)化

        現(xiàn)在讓我們談談優(yōu)化。希望你能明白為什么我們不要過早優(yōu)化!

        • 第一,優(yōu)化可能是無用的工作。因為我們應該先研究整個應用程序,而你的代碼很可能不會成為瓶頸。我們需要的是快速的結果,MVP(Minimum Viable Product,最簡可行產(chǎn)品),然后才會考慮它的問題。
        • 第二,優(yōu)化都必須有所依據(jù)。也就是說,每次優(yōu)化都應該建立在基準上,我們必須證明它給我們帶來了多少利潤。
        • 第三,優(yōu)化也許會帶來復雜。你需要知道的是,大多數(shù)優(yōu)化會使代碼的可讀性變差。你需要把握好這種平衡。

        優(yōu)化建議

        現(xiàn)在我們按照 Go 中的標準實體分類,來給出一些實用建議。

        1. 數(shù)組與切片

        提前為切片分配內(nèi)存

        盡量使用第三個參數(shù):make([]T, 0, len)

        如果不知道元素確切的數(shù)量并且切片是短暫的,可以分配更大一點,保障切片在運行時不會增長。

        不要忘記使用 copy

        盡量不要在復制時使用 append,例如在合并兩個或多個切片時。

        正確迭代

        一個包含許多元素或大元素的切片,使用 for 去獲取單個元素。通過這種方法,將避免不必要的復制。

        復用切片

        如果對傳入的切片進行某種操作并返回已經(jīng)修改的結果,我們可以返回它。這樣能避免新的內(nèi)存分配。

        不要留下不使用的切片部分

        如果需要從切片中切下一小塊并僅使用它,該切片的主要部分也將被保留。正確的做法是,為這小塊切片使用新的副本,而將舊的切片扔給 GC。

        2. 字符串

        正確拼接

        如果拼接字符串可以在一個語句中完成,那就使用 + 操作符。如果需要在循環(huán)中執(zhí)行此操作,使用 string.Builder,并使用它的 Grow 方法預先指定 Builder 的大小,減少內(nèi)存分配次數(shù)。

        轉換優(yōu)化

        string 和 []byte 在底層結構上非常相近,有時這兩種類型之間可以通過強轉換來避免內(nèi)存分配。

        字符串駐留

        可以池化字符串,從而幫助編譯器只存儲一次相同的字符串。

        避免分配

        我們可以使用 map(級聯(lián))而不是復合鍵,我們可以使用字節(jié)切片。盡量不使用 fmt 包,因為它所有的方法都用到了反射。

        3. 結構體

        避免拷貝大結構體

        我們理解的小結構體是不超過4個字段不超過一個機器字大小。

        一些典型的拷貝場景

        • 投射到 interface
        • 通道的接收和發(fā)送
        • 替換 map 中的元素
        • 向切片添加元素
        • 迭代(range)
        避免通過指針訪問結構體字段

        解引用是昂貴的,我們應該盡可能少地這樣做,尤其是在循環(huán)中。同時它也失去了使用快速寄存器的能力。

        處理小結構體

        這項工作由編輯器進行優(yōu)化,這意味著它很便宜。

        使用對齊減小結構體大小

        我們可以對齊結構體(根據(jù)字段的大小,以正確的順序排列它們),以此減小結構體本身的大小。

        4. 函數(shù)

        使用內(nèi)聯(lián)函數(shù)或自己內(nèi)聯(lián)它們

        嘗試編寫可供編譯器內(nèi)聯(lián)的小函數(shù),它會很快,甚至快過自己在函數(shù)中嵌入代碼。對于熱路徑(hot path)尤其如此。

        哪些不會內(nèi)聯(lián)

        • recovery
        • select 塊
        • 類型聲明
        • defer
        • goroutine
        • for-range
        合理地選擇函數(shù)參數(shù)

        嘗試使用小參數(shù),因為它們的復制將被優(yōu)化。嘗試復制和棧增長在GC負載保持平衡。避免大量參數(shù),讓你的程序使用快速寄存器(它們的數(shù)量是有限的)。

        命名返回值

        這似乎比在函數(shù)體中聲明這些變量更高效。

        保存中間結果

        幫助編譯器優(yōu)化你的代碼,保存中間結果,然后會有更多的選項來優(yōu)化你的代碼。

        仔細地使用 defer

        盡量不要使用 defer,或者至少不要在循環(huán)中使用它。

        助力 hot path

        避免在熱路徑分配內(nèi)存,尤其是短生命對象。制作最常見分支(if,switch)

        5. Map

        提前分配內(nèi)存

        和 slice 一樣,初始化 map 時,指定其大小。

        使用空結構體為值

        struct{} 什么都不是(不占內(nèi)存),因此例如傳遞信號時,使用它是非常有益的。

        清空 map

        map 只能增長,不能縮小。我們需要重置 map 時,刪除其所有元素是無濟于事的。

        盡量不在鍵和值中使用指針

        如果 map 中不包含指針,那么 GC 就不會在上面浪費寶貴的時間。字符串也使用了指針,因此應該使用字節(jié)數(shù)組而不是字符串作為鍵。

        減少修改次數(shù)

        同樣,我們不想使用指針,但我們可以使用  map 和 slice 的組合,將鍵存儲在 map 中,將值存在 slice。這樣我們就可以不受限制地更改值。

        6. Interface

        計算內(nèi)存分配

        請記住,要為接口分配值時,首先需要將其復制到某處,然后將指針黏貼給它。關鍵是復制。事實證明,接口的裝箱和拆箱的成本將近似于結構體大小的一次分配。

        選擇最優(yōu)類型

        在某些情況下,接口的裝箱和拆箱期間沒有分配。例如,變量和常量的小值或布爾值、具有一個簡單字段的結構體、指針(包括 map、channel、func)

        避免內(nèi)存分配

        與其他地方一樣,盡量避免不必要的分配。例如將一個接口分配給另一個接口,而不是裝箱兩次。

        僅在需要時使用

        避免在頻繁調(diào)用的函數(shù)參數(shù)和返回結果中使用接口。我們不需要額外的拆裝包操作。減少使用接口方法調(diào)用的頻率,因為它會阻止內(nèi)聯(lián)。

        7. 指針、通道、邊界檢查

        避免不必要的解引用

        尤其是在循環(huán)中,因為事實證明它太昂貴了。解引用是我們不想自費執(zhí)行的操作。

        使用通道效率低下

        channel 同步比其他同步原語方法慢。另外, select 中的 case 越多,我們的程序就越慢。但是,select,case 加 default 有被優(yōu)化。

        避免不必要的邊界檢查

        這也很昂貴,我們應該避免它。例如,只檢查(獲取)一次最大切片索引,而不是多次。最好立即嘗試獲得極端選項。

        總結

        在整篇文章中,我們看到了一些相同的優(yōu)化規(guī)則。

        幫助編譯器做出正確的決定,它會感謝你的。在編譯時分配內(nèi)存,使用中間結果,并盡量保持你的代碼可讀。

        我再次重申,對于隱式優(yōu)化,基準是強制性的。如果因為編譯器在不同版本之間變化太快,昨天工作的東西明天就不能工作,反之亦然。

        不要忘記使用 Go 內(nèi)置的分析和跟蹤工具。

        譯者有話說

        注意,作者的建議并不一定是對的。就像原文中有人評價,為什么不在每條建議下面列出優(yōu)化代碼。因為作者更希望開發(fā)人員把這些建議當做一張備忘單,知道這些瓶頸并主動去尋找如何做優(yōu)化。

        ------------------- End -------------------

        往期精彩文章推薦:

        歡迎大家點贊,留言,轉發(fā),轉載,感謝大家的相伴與支持

        想加入Go學習群請在后臺回復【入群

        萬水千山總是情,點個【在看】行不行

        瀏覽 22
        點贊
        評論
        收藏
        分享

        手機掃一掃分享

        分享
        舉報
        評論
        圖片
        表情
        推薦
        點贊
        評論
        收藏
        分享

        手機掃一掃分享

        分享
        舉報
          
          

            1. 美日韩精品视频 | 欧美69久成人做爰视频 | 国产毛片毛片毛片操逼视频 | 性一交一乱一视一频 | 厨房呻吟嗯用力呀嗯啊 |