Go 高效能系列教程之六:一些建議和實踐

yudotyang發表於2021-06-11

原文連結 https://dave.cheney.net/high-performance-go-workshop/gophercon-2019.html#tips-and-tricks)

這是一些隨機的建議。也是該系列的最後一部分,包含一些微小的優化程式碼的技巧。

6.1 協程

Go 語言中最關鍵的特性就是協程

協程非常易用、建立成本低,你幾乎可以認為是沒有代價的。

Go 的執行時是為擁有成千上萬個協程的程式而編寫的,一個程式包含成千上萬的協程並不奇怪。

但是,每個協程都需要一個最小的協程棧的記憶體空間,目前最小記憶體空間是 2kb。

2048 * 1,000,000 goroutines = 2GB 記憶體,而且他們還沒有做任何事情。

6.1 要知道何時停止 goroutine

協程的的建立和執行的成本非常低。但就記憶體佔用而言,他們的成本確實有限,但也不能建立無限個協程。

在你的程式中,每次你使用go關鍵詞就能啟動一個協程,但你必須要知道如何,以及何時該協程能結束。

在你的程式設計中,某些協程可能會執行直到程式結束才會退出。這些協程非常罕見,但也要遵循該規則)。

如果你不知道何時結束協程,那將會有潛在的記憶體洩漏的隱患。因為協程會將其記憶體以及從棧上可以訪問的任何堆上分配的變數固定在堆記憶體上。

注意:永遠不要開啟一個不知道如何停止的協程

6.1.2 深入閱讀

6.2 對於某些請求,Go 使用高效的網路輪詢演算法

Go 執行時使用有效的作業系統輪詢機制(kqueue,epoll,windows IOCP 等)來處理網路 IO。 一個單一的作業系統執行緒將為許多等待的 goroutine 提供服務。

但是,對於本地檔案 IO,Go 不會實現任何 IO 輪詢。 * os.File 上的每個操作在進行中都會消耗一個作業系統執行緒。

大量使用本地檔案 IO 可能導致您的程式產生數百或數千個執行緒。 可能超出您的作業系統所允許的範圍。

您的磁碟子系統不希望能夠處理成百上千的併發 IO 請求。

要限制併發阻塞 IO 的數量,請使用工作程式 goroutine 池或緩衝的通道作為訊號燈。

var semaphore = make(chan struct{}, 10)

func processRequest(work *Work) {
      semaphore <- struct{}{} // acquire semaphore
      // process request
      <-semaphore // release semaphore
  }

6.3 當心應用程式中的 IO 因子

如果您正在編寫伺服器程式,則它的主要工作是多路複用通過網路連線的客戶端和儲存在應用程式中的資料。

大多數伺服器程式都會接受請求,進行一些處理,然後返回結果。 這聽起來很簡單,但是根據結果,它可能會讓客戶端消耗伺服器上大量(可能是無限制的)資源。 這裡有一些注意事項:

  • 每個傳入請求的 IO 請求數量; 單個客戶端請求生成多少個 IO 事件? 它可能平均為 1,或者如果從快取中提供了許多請求,則可能小於一個。
  • 服務查詢所需的讀取量; 它是固定的,N + 1 還是線性的(讀取整個表以生成結果的最後一頁)。

相對而言,如果記憶體很慢,那麼 IO 太慢了,您應該不惜一切代價避免這樣做。 最重要的是,避免在請求的上下文中進行 IO-不要讓使用者等待磁碟子系統寫入磁碟甚至讀取磁碟。

6.4 使用流式 IO 介面

儘可能避免將資料讀入 [] byte 並將其傳遞。

根據請求,您可能最終將兆位元組(或更多!)的資料讀取到記憶體中。 這給 GC 帶來了巨大壓力,這將增加應用程式的平均延遲。

相反,可以使用 io.Reader 和 io.Writer 介面來構建流式處理以限制每個請求使用的記憶體量。

為了提高效率,如果您使用大量的 io.Copy,請考慮實現 io.ReaderFrom / io.WriterTo。 這些介面效率更高,並且避免將記憶體複製到臨時緩衝區中。

6.6 超時,超時,超時

永遠不要開啟一個不知道耗費多少時間的 IO 操作。

你應該使用 SetDeadline,SetReadDeadline,SetWriteDeadline 函式給每一個網路請求設定超時機制。

6.6 Defer 函式代價很高,不是嗎?

從歷史上看,Defer 函式成本很高,因為它必須將 defer 函式的引數以閉包的形式儲存。

defer mu.Unlock()

等價於

defer func() {
        mu.Unlock()
}()

如果完成的工作量很小,則 defer 會很昂貴,經典示例是將 struct 變數或對映查詢周圍的互斥鎖解鎖。

更多原創文章乾貨分享,請關注公眾號
  • Go 高效能系列教程之六:一些建議和實踐
  • 加微信實戰群請加微信(註明:實戰群):gocnio

相關文章