一個編譯旗標,快 20 倍
在第一個 mesh 跑起來的隔天早上,它正以 1.2 MB/s 把 61.9 GB 的照片庫送過房間。跑完要十四小時。到了早餐時間,它變成 23.8 MB/s——而真正有效的那個修法,說出來有點難為情。

嫌疑犯名單
顯而易見的推論全都指向協定。每個分片都是嚴格一個一個寫:送出、等對方回應、才送下一個——吞吐量被往返延遲鎖死,跟頻寬無關。而 19,000 張照片幾乎每個檔案都只有一個區塊,每個分片都要付完整的一次往返。兩件事都是真的,都值得修。但都不是主角。
然後那個一句話破案的問題來了:「等一下,這兩台 Mac 該不會不是走區網,而是繞 VPN 出去吧?」要確認就得量原始鏈路,於是:在同樣兩台機器之間用 SSH 灌 100 MB。20 MB/s。網路完全沒問題。Recilic 只用了自己早就擁有的那條路的百分之六。
不光彩的答案
開發版是 Debug build。於是我把熱路徑在兩種配置下各跑一次基準測試,每個階段灌 8 MB:
糾刪碼編碼(我們的 Swift):3.2 MB/s → 142.9 MB/s (44×)
ChaChaPoly 加密(CryptoKit):958.8 → 958.5 MB/s (1.0×)
SHA-256(CryptoKit):3260 → 3156 MB/s (1.0×)
Chunk 切塊(我們的 Swift):1965 → 2080 MB/s (1.1×)
整個落差只存在於一個地方:GF(256) 糾刪碼編碼器。加密從來就不慢——CryptoKit 是系統內預先最佳化好的,你的建置設定傷不到它。但編碼器是我們自己的 Swift,每一個 byte 都要透過查找表做乘法。開了最佳化,gfMul 被 inline 進緊迴圈、Data 的引用計數流量消失、whole-module optimization 還能跨檔案 inline 查找表。在 -Onone 下,每個 byte 都要付一次真的函式呼叫加上引用計數。
為什麼一個編譯旗標值 44 倍
想像你要在 660 億個信封上蓋章。編碼器面對的差不多就是這件事:61.9 GB 是 660 億個 byte,而它要對每一個做四次乘法——總共幾千億次微小操作。每次操作本身都極其簡單:查兩個表、把結果合起來。真正決定你這個下午怎麼過的,是每次操作周邊的手續。
Debug 模式是這樣做的:每蓋一個信封,走去倉庫拿印章、走回來、蓋下去、把印章還回倉庫、然後在本子上登記一筆。蓋章是一瞬間的事。走路才是全部。
Release 模式把印章握在手上,本子丟掉。
翻譯回術語:走去倉庫那趟路是函式呼叫,最佳化器會把它inline 直接展開在迴圈裡;本子是自動引用計數(ARC),最佳化器證明它沒必要就刪掉。兩者都沒有改變程式算出什麼——只改變了每個 byte 周圍要走多少儀式。
這也正是 Apple 的加密免疫的原因:它出廠時就是封好的,本來就處於「印章握在手上」的狀態,你的建置設定伸不進去。只有你自己寫的程式碼要繳 debug 稅——而且是每個 byte 繳一次。
而數字對得剛剛好:一個 3.2 MB/s 的編碼器,扣掉 I/O 和封裝開銷,就是我們看到的 1.2–2 MB/s。一個 142.9 MB/s 的編碼器遠高於 20 MB/s 的網路——所以瓶頸搬到了線路上,那本來就是它該待的地方。
開最佳化重編:1.2 → 23.8 MB/s,甚至高於我們用 SSH 量到的 20 MB/s。原本要十四小時的備份現在大約四十五分鐘。瓶頸回到 Wi-Fi 鏈路——它本來就該在那裡。
那些依然值得做的修法
協定層的推論並沒有錯,只是被更大的因素蓋過去了——而它們照樣上線了,因為到了滿速之後,剩下的延遲才真的開始有感:
分片並發寫入。一個區塊的六個分片現在平行寫入,不再排隊。任何一個失敗時,整個 group 仍會被 drain 完,讓每個已經落地的分片都進入清理名單——寫一半的備份絕不能留下孤兒。
連線池。每個節點四條通道,並發寫入的往返互相重疊,而不是全部塞在同一根管子後面排隊。
兩個從真實使用中冒出來的傷口。筆電傳輸中途睡著,過去會讓備份永遠掛在一個死掉的 socket 上——現在 TCP keepalive 加上每個請求的期限,一分鐘內就轉成「節點暫時無法連線」,下次備份自動重連。另外,App 啟動後的第一次連線可能會失敗一次,因為 macOS 正在重建它的區網權限快取;現在它會自己重試,所以沒人會看到。
順手修掉的一個 bug:前置條件缺失時按下「Back Up Now」——什麼都沒發生,而且無聲無息。一個看起來可以按、按下去卻只回你沈默的按鈕,比錯誤訊息更糟。現在它會明確說出缺什麼。
把教訓寫下來
Recilic 0.1.0 已上架 Mac App Store,創始價 $9.99。mesh 會以免費更新推出——而且是滿速的。