讓 Mole 的解除安裝列表一點就出來
在 Mole 的解除安裝頁裡點一個 App,右邊會先空一下,再出現「正在掃描」,過半秒到一秒殘留才列出來,Xcode 每點一次要等三四秒,ChatGPT 要六秒,展開 ChatGPT 往下捲還會一頓一頓的。我想認真看一下這段時間到底花在了哪,順便也翻了幾個開源解除安裝工具是怎麼做的,這些改動在 10 月 5 日做完,已經隨 Mole 1.16.0 正式版發布,下面是當時的實測和取捨。
慢的不是 App 本身
一開始以為 App 越大越慢,測下來並不是,一個 6.6 MB、只有六項殘留的小工具,第一次點開也要 0.56 秒左右。時間主要花在每次點擊都要做的幾件固定的事上,下面是在我的 M5 Pro 上用 Release 版本測的,取三次的中位數:
- 讀取 Homebrew 的 cask 目錄,這份目錄有 5.6 MB,每次大約 200 ms,用來判斷這個 App 是不是用 Homebrew 裝的。
- 校驗 App 的程式碼簽名,而且校驗了兩次,大約每 MB 可執行檔 1 ms,MindNode 每點一次就要 260 ms。
- 最慢的一項掃描是最近使用的文件列表,775 筆記錄大約 230 ms,幾乎都花在逐筆檢查路徑上。
個別 App 還有自己的情況疊在上面,Xcode 的安裝收據有 41 MB,記著 12.5 萬條路徑,每次點開都要重新解析一遍,ChatGPT 會留下大約 15000 個暫存資料夾,每一個都要單獨確認是不是它的。
改了哪些地方
- cask 目錄在打開解除安裝頁時就在背景讀好,不用等到點擊。
- 點擊時只讀簽名資訊、不做校驗,並記下這個 App 套件的檔案標識,完整的簽名校驗放到列表出來以後在背景跑,刪除之前必須跑完。
- 最近使用的文件先按名字篩一遍,名字對得上的才做完整的歸屬檢查。
- 資源庫下的幾個根目錄平行列出,容器的身分按中繼資料檔快取,輔助程式的識別碼前綴每次掃描只算一次。
- 安裝收據按收據檔快取,第二次點 Xcode 不用再解析那 41 MB。
- 名字本身就能證明歸屬的暫存資料夾不再逐個確認,真正刪除時仍然逐個路徑檢查。
- 耗時長的掃描先開始,幾百個小任務排在後面。
- 滑鼠劃過時開始的預掃描,指標一離開就停下,解除安裝之前開始的掃描,也不會在解除安裝以後再把舊結果寫回來。
簽名校驗這一條,最省事的做法是直接刪掉,我沒刪,只是挪到了點擊以後。現在背景校驗沒通過時,App 本身照常解除安裝,只有靠簽名資訊才找到的那幾類殘留會留下來,比如按開發者團隊 ID 比對到的容器,結果裡會標成「無法確認 App 簽名」,沒刪也不算失敗。如果掃描以後 App 被換成了另一個,仍然會拒絕解除安裝。
改完以後快了多少
打開解除安裝頁大約 1.5 秒後第一次點擊,同樣是 Release 版本:
| App | 改之前 | 改之後 |
|---|---|---|
| Shottr | 556 ms | 137 ms |
| MindNode | 1233 ms | 169 ms |
| Raycast | 716 ms | 171 ms |
| Parallels Desktop | 871 ms | 194 ms |
| VS Code | 584 ms | 232 ms |
| 微信 | 605 ms | 238 ms |
| Xcode | 3689 ms | 384 ms |
| ChatGPT | 6134 ms | 1530 ms |
大多數常見 App 現在都在 250 ms 以內,殘留一次列出來,不會先閃一下「正在掃描」,背景的簽名校驗在列表出來以後 12 到 434 ms 內跑完。
ChatGPT 那一萬五千行
速度上來以後,展開 ChatGPT 往下捲還是卡,數了一下,它列出了 15175 行,其中 15143 行是 T/.com.openai.codex.XXXXXX 這種暫存資料夾,一共 1.0 GB,App 每次啟動都會留下幾個。我自己做的 Hina 也類似,344 行裡有 323 行是測試跑完留下的 Hina-<Suite>-<UUID>.plist,加起來才 0.01 MB,兩邊都是一種隨機命名的東西佔滿了整個列表。
卡頓除了行數多,還有兩個原因,展開的那一行每次重繪都要把所有殘留重新排一遍序,滑鼠停在列表上、行從指標下面滑過時,又會觸發整行重繪,於是每滑過一行就再排一次。拿真實的 ChatGPT 資料測,一次重繪從 309 到 354 ms 降到 56 到 58 ms,Hina 從 33 到 36 ms 降到 17 到 19 ms,ChatGPT 展開後的高度也從 3200 pt 縮到了 1225 pt。
現在幾行殘留如果是同一類、在同一個目錄、歸屬同一個 App,預設勾選也一樣,名字只差 App 自己產生的那一段,就會合成一行,這一段可以是隨機後綴、UUID、一長串十六進位或者當機報告裡的時間戳記。合併後的那一行保留類別名稱,路徑裡用 * 代替變化的部分,後面寫上數量和總大小,只有一個勾選框,也不再展開,ChatGPT 變成 13 個類別 33 行,Hina 變成 24 行。
有自己名字的兄弟目錄還是各佔一行,比如 ChatGPT 的七個 Electron 快取 GPUCache、Code Cache、Crashpad 這些,把它們也合併的話,Hina 只剩七行左右,看著反而不像清乾淨了,十幾行是比較合適的量。像 T/<id>.helper 這種名字裡帶個六個字母單字的,也單獨一行,因為只看這六個字母,分不清它是不是隨機後綴。
之前試過的兩種做法這次也沒再用,一種是在已經展開的 App 裡再套一層可以收合的分組,9 月 30 日試過,10 月 2 日就刪掉了,收合裡套收合,相似的路徑還會被分到分組和散行兩邊,另一種是一個類別只放一行,9 月 4 日用過,一週後又改回來,因為它把不同位置的東西藏到了一起。
別的解除安裝工具怎麼做
Pearcleaner 會平行列出大約 62 個根目錄,只按名字比對,不確認歸屬,也沒有快取,它快是因為做得少,騰訊檸檬在進入頁面時就把所有 App 的殘留算好,每天重新整理一次,點擊自然是瞬間的,代價是每次打開都有一次很大的背景掃描,和 Mole 不新增常駐開銷的方向不合,MangoDisk、Uninstally 和 Mole CLI 用的是固定的根目錄加有限的平行數。
翻下來沒有哪個工具有比 Mole 已經在用的 getattrlistbulk 和 readdir 更快的底層辦法。Spotlight 我也試過,查一次要 0.3 到 0.4 秒,還漏掉了大半殘留,VS Code 的四項只找到一項,searchfs 在 APFS 上能用,但一次要 15 秒左右,所以最後都是在現有的掃描上把重複的事拿掉。解除安裝之前怎麼看這份殘留列表,可以接著看解除安裝前先看清楚殘留清單。