Go 語言三個高效編程的技巧

Go 是一種非常不錯的編程語言。它是一種讓你真正的關注自己的業務,不必爲程序本身操心太多的語言,因此您可以儘快編寫應用程序。比如它有比較完整的生態系統,可爲你提供入門所需的一切。

但是呢,它也不是萬能的,有一些需要我們注意。我寫這篇文章主要是爲了提醒我自己,也是一些觀察和總結。當然,這些只是技巧,並不是真正的問題,當然如果你注意到它們並且在實戰中使用,那麼將會讓你非常受益,這是新手和老手的差別。

不要使用 Logrus

這其實和泛型有關。因爲 Go 語言是一門強類型的靜態語言,所以你不可能像 NodeJS 或者 PHP 那樣繞過數據類型。那如果我們還需要使用通用的類型怎麼辦呢?比如像 Loger,或者 ORM,因爲只有使用了通用的類型,才能編寫出通用的代碼,不然每個都要寫一次。

最終,我們只能用反射。而 Logrus 大量使用反射,這導致大量分配計數。雖然通常不是一個大問題(取決於代碼),但性能很重要,尤其是在大規模、高併發的項目中。雖然這聽起來像是一個非常小的優化,但避免反射很重要。如果你看到一些可以不考慮類型而使用結構的代碼,它會使用反射並且會對性能產生影響。

例如,Logrus 並不關心類型,但顯然 Go 需要知道(最終)。Logrus 怎麼辦呢?使用反射來檢測類型,這是開銷。

  log.WithFields(log.Fields{
    "animal": myWhatever,
  }).Info("A walrus appears")

所以我會更喜歡 zerolog,當然 zap 也不錯。兩者都宣稱零分配,這也是我們希望的,因爲它們的性能影響最小。

不要使用 encoding/json

當我們需要一個功能、函數的時候,很多人都建議使用標準庫。但是標準庫中的 encoding/json 模塊是個例外。其實也和上面的例子一樣,encoding/json 使用反射,這會導致性能不高,並且在編寫返回 json 響應的 API 、或者微服務時會造成損失。

比如你可以使用 Easyjson,它很簡單,也很高效,它是使用代碼生成器來創建將結構轉換爲 json 所需的代碼,以最大限度地減少分配。這是一個手動構建步驟,很煩人。有趣的是 json-iterator 也使用反射,但速度明顯更快,我懷疑是黑魔法。

儘可能不要在 goroutine 中使用閉包

比如,下面這個示例代碼:

for i:=0;i<10;i++ {
  go func() {
     fmt.Println(i)
  }()
}

大多數人可能期望這會打印數字 0 到 9,就像將任務委託給 goroutine 時那樣。

但是實際結果:根據系統,你將得到一兩個數字和許多 10。

爲什麼會這樣?閉包可以訪問父作用域,因此可以直接使用變量。儘管更新的 linters 可能會警告你 “變量閉包捕獲”,但並不會要求你重新聲明該變量。

Go 的性能名聲很大程度上歸功於執行的運行時優化,它嘗試 “猜測” 你想要做什麼並優化某些執行路徑。在此期間,它 “捕獲” 變量並以理論上最有效的方式將它們傳遞到需要它們的地方(例如,在完成一些非併發操作以釋放某些 CPU 上的分配之後)。這種情況下的結果是循環可能會啓動 goroutines,goroutines 可能會在很晚之後從父作用域接收到 i 的值。不能保證在多次執行此代碼時你會看到哪個,可能是數字 10,也可以是其他數字。

如果你出於某種原因確實使用了閉包,一定要傳遞變量 i,就像對待每個函數一樣對待閉包。

小結

編程實踐中的技巧肯定不止這三個,在實踐中摸索並掌握他們,可以讓我們的編程能力提升,讓我們可以寫出更優化的代碼。

本文爲原創文章,轉載註明出處, 歡迎掃碼關注公衆號flysnow_org或者網站 https://www.flysnow.org/ ,第一時間看後續精彩文章。覺得好的話,請猛擊文章右下角「在看」,感謝支持。

本文由 Readfog 進行 AMP 轉碼,版權歸原作者所有。
來源https://mp.weixin.qq.com/s/QWYFKskL3ACK5IGNUOqYlQ