這篇是叫 Claude 從我的筆記整理出來,事情真的發生過,字是他打的,連語氣也是叫他學我的,我只負責瞄一下的喔。
覺得網路慢的時候,先看看是不是自己畫得慢
先講這是什麼東西
前幾篇提過的 tang console,再講清楚一點。它是一塊 FPGA 板子,裡面用邏輯閘拼出一顆很小的 RISC-V 處理器來跑 Linux。有多小呢,76 MHz,cache 只有 8 KB,大概是三十年前電腦的等級。螢幕是 HDMI 直接輸出的文字畫面,Wi-Fi 是旁邊一顆 ESP32 小晶片用一條線接過來的。
畫字的程式叫 tangterm,是自己寫的。它底下用了一個叫 libvterm 的現成 library,負責看懂終端機那些控制碼,像是「游標往上兩格」「這行用紅色」「畫面往上捲一行」這種。
用法就是開機、接鍵盤、ssh 到我在東京的 VPS,然後在上面做事。很慢的電腦,很大的字,就是故意的。
症狀:畫面要等好久
ssh 上去之後 cat 一個長一點的檔案,或是開 neovim,畫面就會一行一行慢慢刷,刷好久。第一個反應當然是網路:Wi-Fi 慢吧?那條線慢吧?到東京本來就要一百多 ms 啊。大家都覺得是網路。
好,那就來量。用 seq 吐一萬行數字當測試,分段計時:
| 一萬行數字 | 時間 |
|---|---|
| 從東京傳到板子上 | 大概 1 秒 |
| 板子把這一萬行畫到螢幕上 | 235 秒 |
一秒鐘就全部到了,然後花了快四分鐘畫出來,一顆 core 滿載。網路什麼事都沒有,是我們自己畫得慢。
板子上沒有工具,那就自己做一個
想知道那 235 秒花在哪裡。一般 Linux 上會用 perf 這類工具,它會每隔一小段時間偷看一下程式現在跑到哪一行,看個幾萬次,哪一行被看到最多次,時間就是花在那裡。可是這顆小 Linux 上放不下 perf。
那那,就自己寫一個最陽春的。tangterm 裡面掛一個 timer,每 10 ms 記一下現在跑到哪,收到一個訊號就把紀錄倒出來,拿回電腦上對回函式名稱。大概一百行吧,夠用了。
測試的時候資料是從板子自己的 serial 口直接餵進去的,完全不經過網路,這樣就沒有人可以再怪 Wi-Fi 了。
結果出來,77% 的時間都在 memcpy,就是搬記憶體。從 libvterm 裡面叫出去的。
原因一:每捲一行,就把整個螢幕搬一次
libvterm 在記憶體裡放了一份螢幕的內容,192 欄 × 54 列,每一格記著字、顏色、粗體這些,整份 500 KB。
它處理「畫面往上捲一行」的方式很直接:把整份往上搬一列。就像搬家,每天有一個新東西進門,就把全家的東西都往前挪一格。在這顆小 core 上搬一次 500 KB 大概要 10 ms,seq 每吐一行就搬一次,一萬行就搬了 5 GB。難怪。
修法是不要真的搬。給每一列一個編號的對照表,「畫面上的第一列」其實是「記憶體裡的第幾列」。往上捲一行,就只是把 54 個編號轉一圈,再把空出來的那一列清掉。東西都不用動,改標籤就好。
原因二:只捲一部分畫面的時候,整區重畫
第二個是 tangterm 自己的問題。硬體有一個很快的捲動功能,可是只會整個螢幕一起捲。如果只要捲螢幕的一部分,像 neovim 下面有一條狀態列不動、上面的內容在捲,或是 less,tangterm 就說「這個我不會」,交給 libvterm 的備案,而備案是把那一整塊區域一格一格重畫,一萬個格子,每捲一行就來一次。
後來改成:反正先用硬體把整個螢幕捲了,再把不該動的那幾列貼回去。一行從 23 ms 變 13 ms,再變 2 ms。
結果
| 測試 | 修之前 | 修之後 |
|---|---|---|
300 行彩色的 ls | 4 秒 | 0.5 秒 |
| 300 行很長的純文字 | 8 秒 | 0.4 秒 |
| 3,000 行短行,整個螢幕捲 | 5 秒 | 1 秒 |
| 3,000 行只捲一部分畫面 | 54 秒 | 5 秒 |
Wi-Fi、那條線、ESP32 從頭到尾都沒有關係。現在打一個字到看到它出現,底線就是到東京那 140 ms 的來回,這個是物理,沒得修。
後來又慢回去 4 倍
兩天後加了往上捲看歷史的功能,然後一萬行又變成 15 秒了。再量一次,這次是三件小事疊起來:每捲走一行就把整行 192 格存進歷史,不管那行其實只有四個數字;歷史的空間太大,每存一行就碰到一塊新的記憶體;libvterm 每行要從頭到尾掃兩次 192 格,在這麼小的 cache 上也很有感。
一樣一個一個修,只存到那行最後一個有字的地方、歷史改成緊緊排在一起、每一列記住「這之後都是空白」讓掃描提早停。15 秒變 4 秒。新功能很會偷偷把速度吃回去喔。
路上踩到的
- libvterm 有一個選項看起來就是解藥,裝上去慢了 10 倍。它把捲動的合併拿掉了,變成一列一列搬。
- 兩個測試 script 不小心同時在跑,量出來的數字差 10 倍,而且不會告訴你。量之前先看一下有沒有別人在跑。
- 編譯悄悄失敗,舊的執行檔還在原地,然後就在測舊的程式。現在每次都先確認跑的是哪一個。
- 在板子上用 awk 湊一個 550 KB 的測試檔,跑了六分鐘,看起來就像當掉。這種檔案在電腦上做好再傳過去就好了。
下次怎麼看
- 「畫面很慢」先分成「資料到了沒」跟「畫出來了沒」兩段,各自計時。這次一段 1 秒一段 235 秒,看一眼就知道該往哪邊走了。
- 沒有現成工具的環境,每 10 ms 記一下跑到哪,就夠找到大頭了。
- 測效能的時候把網路拿掉,直接餵資料,不然永遠有人可以說是 Wi-Fi。
- 修完過幾天再量一次,新功能很會偷偷把速度吃回去。
一秒鐘就到了,花了四分鐘畫出來,這不能怪東京啦