前幾天都在算交換器,port-to-port 量到的是幾百 ns,為了它值不值得花那麼多工夫調?要回答這個問題,得先知道封包離開交換器之後還要走多遠。
| 第幾關 | 發生什麼 | 誰在做 |
|---|---|---|
| 1 | 網卡收完 frame,驗 FCS | 硬體 |
| 2 | 資料 DMA 進主機記憶體的 ring buffer | 硬體 |
| 3 | 網卡通知 kernel 有東西進來(中斷或被 polling 撈到) | 軟體(由硬體觸發) |
| 4 | driver 把封包從 ring buffer 取出,往上送 | 軟體 |
| 5 | 網路堆疊拆 IP 跟 TCP/UDP 的 header、查 socket | 軟體 |
| 6 | 資料複製到那個 socket 的接收緩衝區 | 軟體 |
| 7 | 排程器把在等的行程喚醒,read() 才回來 |
軟體 |
七關裡有五關是軟體,這個比例是這篇的重點。 硬體那兩關跟 Day 9 講的「處理」一樣,只能量,但相當固定;軟體那五關取決於當下這台機器上還有誰在跑。
Day 9 說佇列是唯一會自己變的,因為它取決於有多少人在搶同一個出口。主機側是同一件事換一個舞台,這裡搶的是 CPU。
行程被喚醒之後不會立刻執行,它要排隊等 CPU,等的時間就是那顆核心上別人執行的時間。這段完全不在網路設備的控制範圍內,而它可以比整台交換器的延遲大好幾個數量級。
Day 2 有量過一次,負載跟 ping 綁在同一顆核心,ping 每包多等了 454 µs 才送出去,是交換器 363 ns 的一千多倍。
第 3 關還有一個常被忽略的設計,發生中斷代價很高,一秒幾十萬個會把 CPU 吃掉,所以網卡會攢一批再發一次,這叫 interrupt coalescing。批次裡第一個到的封包,要等這批攢滿或計時器到了才被通知,它明明已經躺在記憶體裡了,卻沒人來拿。kernel 這邊的 NAPI 也是同一招:收到第一個中斷之後改用輪詢,把一批撈完再回去等中斷。
這兩個機制都可以調,而且預設值通常是為吞吐調的。
交換器那一端已經有數字了,主機側還沒量。先把預測寫在這裡,後面回來對:
交換器 port-to-port(已量) :約 363 ns
主機側 kernel stack 的收包延遲 :還沒量,預期是微秒等級
七關各自佔多少 :還沒拆開量
我預期在沒人搶 CPU 的情況下,主機側也會比交換器的 port-to-port 大 1-2 個數量級。 理由就是上面那張表,五關軟體,其中一關等中斷、一關等排程器。要是量出來兩邊差不多,這張表就要重畫。
有一個現成的錨點支持這個方向。Day 2 我在同一台機器的 loopback 上跑過一個 UDP echo,回覆要叫醒一個使用者空間行程,往返中位數 5.52 µs;而交換器轉發一個封包是 363 ns。這兩件事不完全對等,UDP echo 那條走的是 loopback 不是網卡,但量級的差距就是這個意思。
先回答開頭那個問題。如果主機側真的大 1-2 個數量級,交換器那幾百 ns 在整條路徑上的佔比就很小。交換器還是要調,只是調的順序要對。主機側沒處理之前,交換器再快也看不出來。
這篇真正能用的地方在變更驗證。交換器的組態相對穩定,改一次要走流程,誰改的、改了什麼都查得到。主機側不是這樣:kernel 更新、驅動更新、有人為了別的服務動了 CPU 設定,這七關的時間就變了,而且沒有人會來通知網路這邊。所以主機側的規則是:
| 什麼時候要量 | 為什麼 |
|---|---|
| 改 ethtool 任何一項的前後 | ring buffer 跟 coalescing 直接影響第 2、3 關 |
| kernel 或驅動更新之後 | 第 4 到 6 關的實作可能換了 |
| 有人動了 CPU 隔離或省電設定 | 第 7 關的等待時間會變 |
| 機器上新增任何服務之後 | 多一個搶 CPU 的人,第 7 關就會抖 |
而且前後要用同一支腳本量。 Day 8 那支腳本量的是交換器,碰不到主機側,但做法一樣:參數寫在設定檔、跟結果一起存,兩次量測條件不一樣的話,差值就沒有意義。
還有一個判讀原則:主機側的延遲要看分布不要看平均。第 7 關是排隊,排隊的分布一定右尾很長,平均值會把最糟的情況藏起來。後面談調校驗證會再展開。
明天講 kernel bypass,也就是把上面那五關整段跳過去的做法。