Golang高質量編程與性能調優(yōu)實戰(zhàn)

高質量編程與性能調優(yōu)實戰(zhàn)

概述

  • 介紹編碼規(guī)范,幫助大家寫出高質量程序

  • 介紹 Go 語言的性能優(yōu)化建議,分析對比不同方式對性能的影響和背后的原理

  • 講解常用性能分析工具 pprof 的使用和工作原理,熟悉排查程序性能問題的基本流程

  • 分析性能調優(yōu)實際案例,介紹實際性能調優(yōu)時的工作內容

內容概要

[圖片上傳失敗...(image-9a539e-1675238053934)]

實踐準備 (必須)

推薦閱讀

高質量編程

簡介

  • 編寫的代碼能夠達到正確可靠、簡潔清晰、無性能隱患的目標就能稱之為高質量代碼

  • 實際應用場景千變萬化,各種語言的特性和語法各不相同,但是高質量編程遵循的原則是相通的

  • 高質量的編程需要注意以下原則:簡單性、可讀性、生產力

常見編碼規(guī)范

代碼格式
  • 使用 gofmt 自動格式化代碼,保證所有的 Go 代碼與官方推薦格式保持一致

總結

  • 提升可讀性,風格一致的代碼更容易維護、需要更少的學習成本、團隊合作成本,同時可以降低 Review 成本
注釋

總結

  • 代碼是最好的注釋

  • 注釋應該提供代碼未表達出的上下文信息

命名規(guī)范
  • variable

    • 簡潔勝于冗長
    • 縮略詞全大寫,但當其位于變量開頭且不需要導出時,使用全小寫
    • 變量距離其被使用的地方越遠,則需要攜帶越多的上下文信息
    • 全局變量在其名字中需要更多的上下文信息,使得在不同地方可以輕易辨認出其含義
  • function

    • 函數(shù)名不攜帶包名的上下文信息,因為包名和函數(shù)名總是成對出現(xiàn)的
    • 函數(shù)名盡量簡短
    • 當名為 foo 的包某個函數(shù)返回類型 Foo 時,可以省略類型信息而不導致歧義
    • 當名為 foo 的包某個函數(shù)返回類型 T 時(T 并不是 Foo),可以在函數(shù)名中加入類型信息
  • package

    • 只由小寫字母組成。不包含大寫字母和下劃線等字符
    • 簡短并包含一定的上下文信息。例如 schema、task 等
    • 不要與標準庫同名。例如不要使用 sync 或者 strings

總結

  • 關于命名的大多數(shù)規(guī)范核心在于考慮上下文

  • 人們在閱讀理解代碼的時候也可以看成是計算機運行程序,好的命名能讓人把關注點留在主流程上,清晰地理解程序的功能,避免頻繁切換到分支細節(jié),增加理解成本

控制流程
  • 避免嵌套,保持正常流程清晰

  • 如果兩個分支中都包含 return 語句,則可以去除冗余的 else

  • 盡量保持正常代碼路徑為最小縮進,優(yōu)先處理錯誤情況/特殊情況,并盡早返回或繼續(xù)循環(huán)來減少嵌套,增加可讀性

總結

  • 線性原理,處理邏輯盡量走直線,避免復雜的嵌套分支

  • 提高代碼的可讀性

錯誤和異常處理
  • 簡單錯誤處理

    • 優(yōu)先使用 errors.New 來創(chuàng)建匿名變量來直接表示該錯誤。有格式化需求時使用 fmt.Errorf
    • github.com/golang/go/b…
  • 錯誤的 Wrap 和 Unwrap

    • 在 fmt.Errorf 中使用 %w 關鍵字來將一個錯誤 wrap 至其錯誤鏈中

    • github.com/golang/go/b…

    • Go1.13 在 errors 中新增了三個新 API 和一個新的 format 關鍵字,分別是 errors.Is、errors.As 、errors.Unwrap 以及 fmt.Errorf 的 %w。如果項目運行在小于 Go1.13 的版本中,導入 golang.org/x/xerrors 來使用。以下語法均已 Go1.13 作為標準。

  • 錯誤判定

  • panic

    • 不建議在業(yè)務代碼中使用 panic
    • 如果當前 goroutine 中所有 deferred 函數(shù)都不包含 recover 就會造成整個程序崩潰
    • 當程序啟動階段發(fā)生不可逆轉的錯誤時,可以在 init 或 main 函數(shù)中使用 panic
    • github.com/Shopify/sar…
  • recover

    • recover 只能在被 defer 的函數(shù)中使用,嵌套無法生效,只在當前 goroutine 生效
    • github.com/golang/go/b…
    • 如果需要更多的上下文信息,可以 recover 后在 log 中記錄當前的調用棧。
    • github.com/golang/webs…

總結

  • panic 用于真正異常的情況

  • error 盡可能提供簡明的上下文信息,方便定位問題

  • recover 生效范圍,在當前 goroutine 的被 defer 的函數(shù)中生效

性能優(yōu)化建議

  • 在滿足正確性、可靠性、健壯性、可讀性等質量因素的前提下,設法提高程序的效率

  • 性能對比測試代碼,可參考 github.com/RaymondCode…

  • slice 預分配內存
    • 在盡可能的情況下,在使用 make() 初始化切片時提供容量信息,特別是在追加切片時
    • 原理
      • ueokande.github.io/go-slice-tr…
      • 切片本質是一個數(shù)組片段的描述,包括了數(shù)組的指針,這個片段的長度和容量(不改變內存分配情況下的最大長度)
      • 切片操作并不復制切片指向的元素,創(chuàng)建一個新的切片會復用原來切片的底層數(shù)組,因此切片操作是非常高效的
      • 切片有三個屬性,指針(ptr)、長度(len) 和容量(cap)。append 時有兩種場景:
        • 當 append 之后的長度小于等于 cap,將會直接利用原底層數(shù)組剩余的空間
        • 當 append 后的長度大于 cap 時,則會分配一塊更大的區(qū)域來容納新的底層數(shù)組
      • 因此,為了避免內存發(fā)生拷貝,如果能夠知道最終的切片的大小,預先設置 cap 的值能夠獲得最好的性能
    • 另一個陷阱:大內存得不到釋放
      • 在已有切片的基礎上進行切片,不會創(chuàng)建新的底層數(shù)組。因為原來的底層數(shù)組沒有發(fā)生變化,內存會一直占用,直到沒有變量引用該數(shù)組
      • 因此很可能出現(xiàn)這么一種情況,原切片由大量的元素構成,但是我們在原切片的基礎上切片,雖然只使用了很小一段,但底層數(shù)組在內存中仍然占據(jù)了大量空間,得不到釋放
      • 推薦的做法,使用 copy 替代 re-slice
  • map 預分配內存
    • 原理
      • 不斷向 map 中添加元素的操作會觸發(fā) map 的擴容
      • 根據(jù)實際需求提前預估好需要的空間
      • 提前分配好空間可以減少內存拷貝和 Rehash 的消耗
  • 使用 strings.Builder
    • 常見的字符串拼接方式
      • strings.Builder
      • bytes.Buffer
    • strings.Builder 最快,bytes.Buffer 較快,+ 最慢
    • 原理
      • 字符串在 Go 語言中是不可變類型,占用內存大小是固定的,當使用 + 拼接 2 個字符串時,生成一個新的字符串,那么就需要開辟一段新的空間,新空間的大小是原來兩個字符串的大小之和
      • strings.Builder,bytes.Buffer 的內存是以倍數(shù)申請的
      • strings.Builder 和 bytes.Buffer 底層都是 []byte 數(shù)組,bytes.Buffer 轉化為字符串時重新申請了一塊空間,存放生成的字符串變量,而 strings.Builder 直接將底層的 []byte 轉換成了字符串類型返回
  • 使用空結構體節(jié)省內存
    • 空結構體不占據(jù)內存空間,可作為占位符使用
    • 比如實現(xiàn)簡單的 Set
      • Go 語言標準庫沒有提供 Set 的實現(xiàn),通常使用 map 來代替。對于集合場景,只需要用到 map 的鍵而不需要值
  • 使用 atomic 包
    • 原理
      • 鎖的實現(xiàn)是通過操作系統(tǒng)來實現(xiàn),屬于系統(tǒng)調用,atomic 操作是通過硬件實現(xiàn)的,效率比鎖高很多
      • sync.Mutex 應該用來保護一段邏輯,不僅僅用于保護一個變量
      • 對于非數(shù)值系列,可以使用 atomic.Value,atomic.Value 能承載一個 interface{}
總結
  • 避免常見的性能陷阱可以保證大部分程序的性能

  • 針對普通應用代碼,不要一味地追求程序的性能,應當在滿足正確可靠、簡潔清晰等質量要求的前提下提高程序性能

性能調優(yōu)實戰(zhàn)

性能調優(yōu)簡介

  • 性能調優(yōu)原則
    • 要依靠數(shù)據(jù)不是猜測
    • 要定位最大瓶頸而不是細枝末節(jié)
    • 不要過早優(yōu)化
    • 不要過度優(yōu)化

性能分析工具

性能調優(yōu)的核心是性能瓶頸的分析,對于 Go 應用程序,最方便的就是 pprof 工具

性能調優(yōu)案例

  • 基本概念
    • 服務:能單獨部署,承載一定功能的程序
    • 依賴:Service A 的功能實現(xiàn)依賴 Service B 的響應結果,稱為 Service A 依賴 Service B
    • 調用鏈路:能支持一個接口請求的相關服務集合及其相互之間的依賴關系
    • 基礎庫:公共的工具包、中間件
  • 業(yè)務優(yōu)化
    • 流程
      • 建立服務性能評估手段
      • 分析性能數(shù)據(jù),定位性能瓶頸
      • 重點優(yōu)化項改造
      • 優(yōu)化效果驗證
    • 建立壓測評估鏈路
      • 服務性能評估
      • 構造請求流量
      • 壓測范圍
      • 性能數(shù)據(jù)采集
    • 分析性能火焰圖,定位性能瓶頸
      • pprof 火焰圖
    • 重點優(yōu)化項分析
      • 規(guī)范組件庫使用
      • 高并發(fā)場景優(yōu)化
      • 增加代碼檢查規(guī)則避免增量劣化出現(xiàn)
      • 優(yōu)化正確性驗證
    • 上線驗證評估
      • 逐步放量,避免出現(xiàn)問題
    • 進一步優(yōu)化,服務整體鏈路分析
      • 規(guī)范上游服務調用接口,明確場景需求
      • 分析業(yè)務流程,通過業(yè)務流程優(yōu)化提升服務性能
  • 基礎庫優(yōu)化
    • 適應范圍更廣,覆蓋更多服務
    • AB 實驗 SDK 的優(yōu)化
      • 分析基礎庫核心邏輯和性能瓶頸
      • 完善改造方案,按需獲取,序列化協(xié)議優(yōu)化
      • 內部壓測驗證
      • 推廣業(yè)務服務落地驗證
  • Go 語言優(yōu)化
    • 適應范圍最廣,Go 服務都有收益
    • 優(yōu)化方式
      • 優(yōu)化內存分配策略
      • 優(yōu)化代碼編譯流程,生成更高效的程序
      • 內部壓測驗證
      • 推廣業(yè)務服務落地驗證

參考資料

?著作權歸作者所有,轉載或內容合作請聯(lián)系作者
【社區(qū)內容提示】社區(qū)部分內容疑似由AI輔助生成,瀏覽時請結合常識與多方信息審慎甄別。
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發(fā)布,文章內容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。

相關閱讀更多精彩內容

友情鏈接更多精彩內容