Golang Performance Optimization Report

Overview

This report summarizes three notable performance optimization pull requests merged in the past two weeks across popular open-source Golang projects. Each PR targets a specific bottleneck — ranging from algorithmic complexity reduction to in-memory caching — and delivers measurable, significant speedups.

從 Golang 1.22 routing enhancement 看專案開發

這次 Golang 1.22 版本針對內建的 http.ServeMux (HTTP request multiplexer) 進行改善,原本 http.ServeMux 的設計力求直覺單純,適用在架設簡單 HTTP server 的應用場景;但隨著 RESTful API 成為目前 HTTP-based interface 設計主流,而既有的 http.ServeMux 並沒有辦法實作 RESTful API,導致內建的 http.ServeMux 使用時機愈來愈少,Gopher 必須仰賴第三方套件才能實現 RESTful API。為了改善此問題,Golang 1.22 加強了 http.ServeMux 的 routing 設計,除了能夠辨別 HTTP method 之外,也增加了動態路徑 wildcard 的功能,使 http.ServeMux 能夠滿足實務上基本 HTTP server 的需求。

The issue of uploading a large-size file using HTTP formdata

接續前面文章提及到,在上傳檔案的使用情境之下,因為 user request 數量增多,造成 service lead time 大幅提升的問題;而導致 lead time 增加的因素,根據實驗數據,主要是因為:

  • The different implementation methods of receiving the file
  • The concurrency model of language runtime

若要從根本來改善此問題,需要從兩個層面來著手:

  • Client side: user 如何傳送夾帶檔案內容的 request 到 server
  • Server side: server 如何依據 client 發送 request 的流程來處理此檔案

透過使用者訪談後,我們預期的使用情境,包含:

Python FastAPI FormData 效能議題

在實作檔案上傳並加解密的服務時,遇到了 user request 數量增多,造成 lead time 大幅提升的問題。服務本身是使用 Python + FastAPI framework 實作,在排除了網路頻寬問題和 server 效能問題後,懷疑是 Python 或 framework 導致延遲時間拉長,所以就決定從此地方著手進行 benchmark 實驗,來觀察瓶頸是發生在何處。

由於最終目的還是希望能找出改善的方式,而既然認為問題是出在 Python 和 framework 上,這次實驗就會先使用 FastAPI 與 aiohttp 寫的 HTTP API service 進行效能比較;此外也使用 Golang 實作的版本來測量不同語言之間的效能落差會到多少。

While implementing a file upload service with encryption and decryption, we encountered an issue where an increase in user requests significantly increased the lead time. The service is built using Python and the FastAPI framework. After ruling out network bandwidth and server performance issues, we suspect that the delay may be due to Python or the framework. Therefore, we decided to start with a benchmarking experiment in this area to identify where the bottleneck occurs.

Golang slice append 實作細節

用 Golang 刷 leetcode 題目時,如果不太清楚 Golang slice 與相關 function 的運作原理,很容易踩到坑,尤其是使用其他高階語言的開發者,剛轉換到 Golang 時會覺得為什麼同樣的程式邏輯,但是出來的結果卻不一樣。因此本篇簡單說明 Golang 最常使用到的 slice append function 運作原理,並且使用 objdump 來觀察記憶體操作狀況。

Slice internal

首先推薦先閱讀 Golang blog Go Slices: usage and internals,雖然距離文章發布的日期已久遠,但是 slice internal 結構還是可以參考。文中很重要的一個概念是:A slice is a descriptor of an array segment., 雖然我們使用 slice 方式很像 array ,但實際上 slice 是一個 descriptor struct ,其中會有一個 array pointer 指向真正存放數值的 array 中。

Run Go applications on Raspberry Pi Pico using TinyGo

TinyGo - Go compiler for small places. TinyGo 自 2019 年正式公開以來,就逐漸受到關注,尤其是 Google 在 2019 年 10 years of Go 也有特別提到 Go 原本將目標放在網路或是雲端等應用程式,不過未來期望能夠更廣泛地應用在 microcontroller 上。在近期 TinyGo 開始支援 Raspberry Pi Pico,再加上 TinyGo 架構也逐漸成熟,因此就來試用看看。

Run applications on Raspberry Pi Pico

在分析 TinyGo 如何編譯適合跑在 Raspberry Pi Pico 的 Go program 之前,先來複習如何讓 user application 在 Raspberry Pi Pico (以下皆簡稱為 Pico) 上執行。在 Pico 進入 user application 的 entry point 之前,會有幾個前置流程,包含:

Switch to a register-based calling convention for Go functions

自己對於 memory layout 相關議題都蠻感興趣的,而這次 Go 1.17 有一項效能改善的 proposal: switch to a register-based calling convention for Go functions 剛好跟 memory 有相關,因此就看了一下 proposal 文件介紹,不但複習了在計算機架構中曾接觸到的 calling convention 知識,也對於 Go 內部機制有更多認識。

Application Binary Interface (ABI)

在談 calling convention 之前,先來談 ABI。Calling convention 是 application binary interface (ABI) 的一部分,而定義 ABI 最主要的目的是建立應用程式與其他應用程式或是與作業系統服務之間低階溝通方式 (依賴 machine code) ,讓應用程式能夠在特定的環境下正確執行。

GopherCon TW 2020 場務組心得

前言

從 2020 年初就開始籌備的第一屆 GopherCon TW ,終於在 11/14(六) 美好地落幕。第一次籌備就遇上疫情問題,導致時程一再延遲,還有首次主辦 conference 經驗不足,有一些遺漏以及沒有注意到的地方,所以自認籌辦期間沒有做的很好 TT

vault

不得不說本次能籌辦成功,多虧總召 David 大力的主持(五星吹捧),時程安排、籌辦會議、以及細節等都處理的很好,並且非常有組織和號召力,難怪是一個好主管,非常敬佩他,希望自己也能在這部分多加油!

會議規模

預估參加人數:300 人 (比例:男 9 女 1) 議程軌: 1 主軌 / 1 天

租用場地

我們是 6 個月前預訂場地,不過有些更適合(便宜)的場地已被租出去,如果可以在 9 個月前預訂(3季的時間)會更好。

另外,因為是第一屆 GopherCon,且只有一個議程軌,希望能有一個好的開場,因此場地就朝階梯式大型會議廳的方向尋找,而會議當天呈現出來的視覺感受和體驗也確實不錯,缺點是費用真的相對較高,而且能容納的人數有限,往後如果參加的人數更多,還是會找其他非階梯式的會議廳。

攤位規劃

在租用場地時,一定要連同攤位規劃一起討論,例如會議廳外可不可以擺設攤位,攤位的數量等。由於我們這次租用的會議廳外無法擺設太多攤位,因此我們額外租用一間較小的會議廳來設置攤位,但就當天情況來說,所有的攤位都在一間房間內,出入口又不大,所以當人多的時候就顯得很擁擠,這是我覺得後續可以改善的地方。

另外,這次租用的場地,牆壁材質無法供廠商黏貼海報或其他宣傳物品,雖然可以用大頭針固定在牆上,但是會對廠商的宣傳物造成破壞,因此讓他們只好捨棄一些擺設,這也是未來租場地時要注意的地方,如果真的無法粘貼,應在攤位規劃說明書上註明清楚,才不會讓他們白帶一趟。

最後,我們本來以為在走道上的攤位會有最多曝光,沒想到參加者在休息時間都往擺設飲食的區域移動,完全不理會經過的攤位 QQ 讓我重新學到食物的威力有多驚人,也很抱歉沒有讓贊助商有預期的廣宣量。

Vault Initialization and Keys Security

預備知識

在閱讀下方內容之前,建議先對 Vault 架構有個基本了解,相關官方文章如下:

  1. Vault Architecture
  2. Vault Key Rotation
  3. Vault Seal/Unseal

另外, Barrier 這個元件在 Vault 相當重要,在 Vault Architecture 頁面中有提及,可以先了解其用意,再來看它是如何被實現。

問題

Vault 在進行 unseal 流程時,其中最重要的就是將加密過的 master key 解密,接著再利用 master key 解出 encryption Key,並且建立起 barrier 防護,讓 storage backend 的資料可以安全地來往於 Vault server 與 storage backend 之間。

COSCUP 2020 - Goroutine stack and local variable allocation in Go

Q&A 補充

Slides: Goroutine stack and local variable allocation in Go

這次分享中,有人詢問我在 stack growing 的時候, user stack 和 system stack 是如何交替切換的。由於當時沒有仔細看 switch 流程,無法很清楚地答覆他,所以事後又看了一下這段,並且畫出簡圖:

hugo

首先,在 curg (current goroutine) func1 呼叫 func2 後,發現 user stack 空間不足,此時先儲存 func1 的 state 到 M 的 morebuf struct 中(用來 bug tracing),接著把 func2 的 state (就是當前 goroutine 的執行狀態,包含 stack pointer, program counter 等)存在 curg 的 sched struct 中,接著 switch 到 g0,從 g0 的 sched 取出 stack pointer 的值 (system stack) 並且設定,接著就可以執行 newstack function。