本頁由機器翻譯,可能有錯誤。 閱讀英文原文

文件

效能報告

NSH 應用程式的實測成本。NSH 如何維持高效能,以及如何量測你自己的應用程式,請參閱效能。

#基準數據

以下取自 NSH 自身的基準測試(除非另有註明,皆為 Apple silicon):

情境數據
計數器應用程式,headless(debug)首幀約 0.5–0.65 ms(58 個 ops/49 個 mount items);一次點按約 0.15 ms
計數器應用程式,Mac Catalyst(release)首幀約 1.05–1.15 ms(其中約 0.7 ms 用於首次以 CoreText 排版 10 段文字);一次點按約 0.12 ms
Gallery 照片清單,headless(debug)首幀 653 個 ops/224 個節點,約 2.7 ms
捲動照片清單,Catalyst(release)緩慢捲動時重新繫結一列約 0.4 ms;快速甩動時整個畫面約 32 列為 0.9–1.6 ms
捲動照片清單,Android 模擬器(arm64、release)重新繫結一列約 0.3–0.4 ms(JS 約 60 µs)
引擎啟動(release)約 75–155 µs;全域 API(fetch、URL 等)在首次使用前不產生任何成本,首次使用後約 90 µs
Hot reload從儲存檔案到新版本開始執行約 120 ms;套用更新 0.2–0.5 ms

#Linux(GTK 4)

在 Linux 桌面(x86_64、24 執行緒、NVIDIA GPU)上以 release 版本量測,Gallery 位於 120 Hz 顯示器上 1896 × 1030 的視窗中,所有讀數皆由 examples/gallery/tools/perf.py(nsh test --target linux --fps,每秒一行;CPU 為整個行程的用量,以單一核心的百分比表示)自動取得。Frame p95 是主執行緒每個已繪製影格所花時間的第 95 百分位數:低於 8.3 ms 時,影格即可趕上 120 Hz 的更新週期。

#動畫範例

範例FPSFrame p95CPU
Stocks:22 條即時 sparkline、一張日內走勢圖、委託簿、成交明細117–1205–6 ms25–29%(1200 × 800)、28–32%(1896 × 1030)
Stocks,視窗以 1.6× 縮放填滿 4K 144 Hz 顯示器143–1454.1–4.4 ms26–29%
Canvas:一個儀表、一個時鐘、一個點陣1202.6–4.4 ms5–10%
Animations:三個原生迴圈、彈簧、序列動畫1202.0 ms12–13%

#每個頁面

Gallery 的每個頁面,在開啟時以及測試捲動它時的表現,視窗為 1200 × 800,顯示器為 144 Hz(同一台機器)。NSH 只在畫面上有內容變動時才繪製影格,因此這裡的 FPS 是有內容在移動時的速率:即已繪製影格之間間隔中位數所對應的每秒影格數(perf.py 的 moving);在顯示器的 144 Hz 下,動畫或捲動的每個影格都在前一個影格之後一個更新週期出現。Over one refresh 是耗時超過一個更新週期(6.9 ms)的影格所占比例。Images 的 GIF 與 WebP 以其自身的影格率播放(此處約 50 FPS),影片亦同。Physics、Overlays 與 Menus 的內容較短:在 1200 × 800 的視窗中,捲動幾個影格就會到底,因此改以 900 × 500 的視窗量測,讓它們有足夠的捲動空間。

頁面影格來源FPSFrame p95Over one refreshCPU
Stocks即時資料、原生迴圈1444.1 ms0%26%
Stocks,捲動即時資料、捲動1444.1 ms0%25%
Photos,捲動捲動1442.8 ms2%9%
Lists,捲動捲動1431.7 ms0%7%
Scrolling,捲動捲動1441.6 ms1%4%
Navigation,捲動捲動1431.7 ms0%2%
Form,捲動捲動1441.3 ms0%4%
Resizable,捲動捲動1430.6 ms0%1%
Dock,捲動捲動1420.5 ms1%1%
Gestures彈簧、脈動光環1441.6 ms0%1–5%
Physics,捲動捲動(900 × 500 視窗)1441.6 ms1%1%
Animations原生迴圈1442.6 ms0–1%5%
Canvas,捲動捲動、原生迴圈1444.2 ms0%1–4%
Styles,捲動捲動1442.3 ms2%3%
Tailwindanimate-* 工具類別1441.7 ms0%3–8%
Text,捲動捲動1441.9 ms0%4%
Text View,捲動捲動1411.8 ms2%7%
Images,捲動捲動、GIF 與 WebP 影格1432.9 ms2%8%
Components,捲動捲動1441.4 ms0%3%
Overlays,捲動捲動(900 × 500 視窗)1441.7 ms0%2%
Menus,捲動捲動(900 × 500 視窗)1441.9 ms0%2%
Controls轉場1440.9 ms0%1–4%
Web and video影片影格、頁面135–1421.0 ms0%7–8%

#記憶體

閒置狀態,啟動後 3 秒(release runner,480 × 640 視窗)。Private 是行程獨自擁有的記憶體(USS);resident 則再加上共用函式庫(GTK、GPU 驅動程式)的分頁,機器上所有 GTK 應用程式都共用這些分頁:

應用程式渲染器Private(USS)Resident(RSS)
Rust 應用程式(nsh init --template rust)GL(ngl)約 49 MB約 142 MB
Rust 應用程式Cairo(GSK_RENDERER=cairo)約 28 MB約 95 MB
TSX 計數器(examples/counter)GL約 79 MB約 166 MB
僅含一個標籤的 GTK 4 視窗,作為對照Vulkan / Cairo約 62 / 約 40 MB約 132 / 約 109 MB

重複造訪同一頁面不會增加記憶體;首次造訪某個頁面時,行程會成長,增加的部分來自它刻意快取的內容(已解碼的圖片,上限 128 MB),以及首次使用時載入的函式庫(web view 所用的 WebKitGTK)。

#應用程式大小

以 nsh init 搭配各個範本建立的 hello world 應用程式,以及兩個實際有人使用的應用程式,皆由 nsh package --target linux 針對 x86_64 建置。Runner 是應用程式唯一的可執行檔;archive 則是它發佈時所用的 .tar.gz。Hello world 皆啟用連結時期最佳化並已 strip(CARGO_PROFILE_RELEASE_LTO=fat、CARGO_PROFILE_RELEASE_CODEGEN_UNITS=1、CARGO_PROFILE_RELEASE_STRIP=true):

應用程式開發語言RunnerArchive
Hello world(--template rust)Rust16.5 MB7.5 MB
Hello world(--template ts)TSX,基於 QuickJS25.7 MB11.6 MB
Hello world(--template mixed)TSX 與一個 Rust crate25.8 MB11.7 MB
sysmon 0.1.3Rust20.9 MB(已 strip)9.7 MB
Omasend 0.2.0Rust22.3 MB9.8 MB

TSX 的 runner 內含 QuickJS 執行環境、原生模組與渲染器;應用程式自身的程式碼則是放在 runner 旁的 bundle,hello world 的 bundle 不到 1 KB。

這些數字的成因:

  • 渲染器:在 NVIDIA 驅動程式下,NSH 使用 GSK 的 GL 渲染器繪製,其影格成本僅為該環境下 Vulkan 渲染器的一小部分(Animations 範例:每個影格約 1 ms 的繪製時間,而非約 9.5 ms)。最終仍由 GSK_RENDERER 決定。
  • Canvas 點陣化:每個 canvas 只會點陣化一次,存成保留在 GPU 上的 256 px 圖磚。動畫值只會重繪讀取它的操作所涵蓋的區域,並疊在繪圖靜態部分已保留的像素之上;位於畫面之外的 canvas 會等到捲入視野時才處理。
  • 路徑只需擱置一次:在長路徑之後呼叫 fillRect 與 fillText,會讓該路徑保留原位,直到再次使用為止(先畫一條 400 點的折線,再畫 240 根長條:0.63 ms)。

#macOS(AppKit)

在 MacBook Pro(Apple M3、24 GB)的內建 120 Hz ProMotion 顯示器上以 release 版本量測,Gallery 位於 1200 × 800 的視窗中,所有讀數皆由 examples/gallery/tools/perf.py(nsh test --target macos --fps,每秒一行;CPU 為整個行程的用量,以單一核心的百分比表示,GPU 為其占用 GPU 的比例)自動取得。各階段為主執行緒每個影格的耗時:tick(display link 的原生動畫)與 canvas(重播繪圖)。

#動畫範例

範例FPSFrame p95CPUGPU
Stocks:22 條即時 sparkline、一張日內走勢圖、委託簿、成交明細1202.7–3.7 ms33–36%18–22%
Canvas:一個儀表、一個時鐘、一個點陣119–1200.6–0.7 ms4–7%0%
Animations:三個原生迴圈、彈簧、序列動畫119–1200.3–1.6 ms5–8%0%

在 Stocks 上,每個影格的主執行緒時間約 1.6 ms 用於 tick,其中約 0.45 ms 用來重播各 canvas 的動畫部分;每個 canvas 的其餘部分由 Core Animation 在其自身的執行緒上繪製。

#每個頁面

Gallery 的每個頁面,在開啟時以及測試捲動它時的表現,視窗同為 1200 × 800。與 Linux 相同,FPS 是有內容在移動時的速率(即已繪製影格之間間隔中位數所對應的每秒影格數);在顯示器的 120 Hz 下,動畫或捲動的每個影格都在前一個影格之後一個更新週期出現。Over one refresh 是耗時超過一個更新週期(8.3 ms)的影格所占比例。靜止的頁面不會繪製影格,CPU 用量為 2–5%。Images 的 GIF 與 WebP 由 Core Animation 播放,影片由 AVFoundation 播放,皆以其自身的影格率進行。

頁面影格來源FPSFrame p95Over one refreshCPU
Stocks即時資料、原生迴圈1203.2 ms0%35%
Stocks,捲動即時資料、捲動1203.5 ms0%36%
AI Chat,捲動捲動1202.4 ms1%5%
Photos,捲動捲動1201.4 ms0%10%
Lists,捲動捲動1201.6 ms0%8%
Scrolling,捲動捲動1202.7 ms0%6%
Navigation,捲動捲動1200.2 ms0%4%
Form,捲動捲動1200.6 ms0%5%
Resizable,捲動捲動1200.3 ms0%4%
Dock,捲動捲動1200.3 ms1%5%
Gestures彈簧、脈動光環1200.4 ms0%4%
Physics,捲動捲動1200.3 ms0%4%
Animations原生迴圈1200.3 ms0%5%
Canvas,捲動捲動、原生迴圈1201.2 ms0%7%
Styles,捲動捲動、搖擺的 transform1201.0 ms0%6%
Tailwindanimate-* 工具類別1200.5 ms0%4%
Text,捲動捲動1200.3 ms0%5%
Text View,捲動捲動1200.7 ms0%5%
Images,捲動捲動1200.4 ms0%5%
Components,捲動捲動1200.3 ms0%5%
Overlays,捲動捲動1200.3 ms0%4%
Menus,捲動捲動1200.3 ms0%5%
Controls轉場1200.3 ms0%4%
Web and video,捲動捲動1200.2 ms0%6%
SystemrequestAnimationFrame、計時器1200.2 ms0%3%
Standard APIs,捲動捲動1200.3 ms0%5%
Storage,捲動捲動1200.3 ms0%5%

#記憶體

閒置狀態,啟動後 3 秒(release runner)。Footprint 是 macOS 計入該行程的記憶體(即「活動監視器」顯示的數值);resident 則再加上系統共用函式庫的分頁:

應用程式視窗FootprintResident(RSS)
Rust 應用程式(nsh init --template rust)1200 × 800約 19 MB約 86 MB
TSX 計數器(examples/counter)1200 × 800約 20 MB約 89 MB
僅含一個標籤的 AppKit 視窗,作為對照480 × 640約 17 MB約 80 MB

在 Gallery 的導覽過程中,行程在到達 Canvas 頁面之前占用 84–230 MB,從 Text View 頁面起則約為 350–360 MB,這包含它刻意快取的內容(已解碼的圖片,上限 128 MB;Text View 頁面的文件),以及首次使用時載入的框架(web view 所用的 WebKit);Canvas 頁面的點陣圖在三個 canvas 同時顯示於畫面時最高可達約 310 MB,頁面關閉後即會釋放。

#App 大小

nsh init 以每個範本建立的 hello world 應用程式,依 nsh package --target macos 為 Apple silicon (arm64) 建置的結果(啟用連結時期最佳化,並如同在 Linux 上一樣移除符號),加上上述兩個以 Apple silicon 版本發行的應用程式。可執行檔是 .app 內的 runner;zip 是壓縮後的 .app(ditto -c -k):

應用程式開發語言可執行檔壓縮後 .app
Hello world(--template rust)Rust12.4 MB6.0 MB
Hello world(--template ts)TSX,基於 QuickJS19.8 MB9.5 MB
Hello world(--template mixed)TSX 與 Rust crate19.8 MB9.5 MB
sysmon 0.1.3Rust21.6 MB8.4 MB(其發行版本的 .tar.gz)
Omasend 0.2.0Rust18.4 MB9.7 MB(其發行版本的 .tar.gz)

這些數字的成因:

  • 只為變化產生影格:原生動畫執行期間,display link 會持續觸發,動畫結束後即停止;動畫會隨頁面一同停止(其 view 被刪除時,關鍵影格隨之結束;迴圈則在其元件的 cleanup 中停止)。
  • 在成本最低之處繪製 canvas:經常重繪的 canvas(動畫、即時資料)會在 Core Animation 的執行緒上繪製(drawsAsynchronously),讓主執行緒保持空閒;偶爾才重繪的 canvas 則立即繪製,不佔用非同步繪製所保留的圖形記憶體。動畫值變化時,只會重繪讀取該值的操作所涵蓋的區域。
  • 保留靜態的 canvas 部分:繪圖內容維持不變後,第一個動畫操作之前的一長段內容會保留為一個圖層;每個影格繪製該圖層,並只重播其餘的動畫部分,直接在主執行緒上立即完成(Canvas 頁面的儀表、時鐘與點陣:不需 GPU 運算,記憶體減半)。
  • 內容變動時才繪製:文字與 canvas 會繪製到各自的圖層中,僅在內容或大小改變時重繪,移動或捲動時則不會。

#Windows(Win32、Direct2D)

在 Windows 11 桌機(Intel Core i7-13700KF、32 GB、NVIDIA GeForce RTX 3060 Ti)上量測,顯示器為 4K 144 Hz、縮放 150%,使用 release 建置,gallery 位於 1200 × 800 的視窗中(1800 × 1200 像素),所有讀數皆由 examples/gallery/tools/perf.py 自動擷取(nsh test --target windows --fps,每秒一行;CPU 為整個行程的使用量,以單一核心的百分比表示)。各階段為主執行緒每個影格的耗時:paint(Direct2D 繪製視窗),以及其中的 canvas(錄製與重播繪圖)。

#動畫範例

範例FPS影格 p95每影格 Paint / canvasCPU
Stocks:22 條即時走勢線、一張盤中圖表、委託簿、成交明細1443.7–4.3 ms1.4 / 0.13 ms36–43%
Canvas:儀表、時鐘、點陣1441.0–2.0 ms0.8–1.4 / 0.05 ms21–35%
Animations:三個原生迴圈、彈簧、序列1441.0–3.6 ms0.9–1.9 ms20–41%

Stocks 每秒提交 16–17 次,每次在引擎執行緒上最多花費 2.1 ms 於 JS,以及 1.7 ms 於核心(p95)。

#每個頁面

gallery 的每個頁面,在同樣 1200 × 800 的視窗中,涵蓋頁面開啟時以及測試捲動它時的情況。與 Linux 和 macOS 相同,FPS 是有東西在移動時的速率(以已繪製影格之間的間隔中位數換算為一個影格);在顯示器的 144 Hz 下,動畫或捲動的每個影格都在上一個影格之後的一次更新週期出現。Over one refresh 是耗時超過一次更新週期(6.9 ms)的影格所占比例。靜止的頁面不會繪製影格,CPU 使用率為 0–2%。Images 的 GIF 與 WebP 依其自身的影格率播放(此處約 40 FPS)。Physics、Overlays、Menus 與 Storage 是在 900 × 500 的視窗中量測,以便有空間可捲動。

頁面影格來源FPS影格 p95超過一次更新週期CPU
Stocks即時資料、原生迴圈1443.9 ms0%36%
Stocks, scrolling即時資料、捲動1444.3 ms0%43%
Photos, scrolling捲動1432.1 ms0%9%
Lists, scrolling捲動1442.0 ms1%19%
Scrolling, scrolling捲動1432.1 ms0%7%
Navigation, scrolling捲動1440.8 ms0%9%
Form, scrolling捲動1440.8 ms0%12%
Resizable, scrolling捲動1430.4 ms0%4%
Dock, scrolling捲動1440.5 ms1%5%
Gestures彈簧、脈動圓環1441.8 ms0%36%
Physics, scrolling捲動(900 × 500 視窗)1440.8 ms0%4%
Animations原生迴圈1441.0 ms0%28%
Canvas, scrolling捲動、原生迴圈1442.0 ms0%35%
Styles, scrolling捲動、擺動的 transform1442.1 ms0%39%
Tailwindanimate-* 工具類別1440.6 ms0%19%
Text, scrolling捲動1441.6 ms0%8%
Text View, scrolling捲動1441.1 ms0%10%
Images, scrolling捲動、GIF 與 WebP 影格1434.8 ms2%23%
Components, scrolling捲動1441.0 ms1%11%
Overlays, scrolling捲動(900 × 500 視窗)1440.6 ms0%1%
Menus, scrolling捲動(900 × 500 視窗)1440.6 ms0%2%
Controls轉場117–1441.5 ms0%22%
Web and video, scrolling捲動1441.6 ms0%15%
SystemrequestAnimationFrame、計時器144< 0.1 ms0%1%
Storage, scrolling捲動(900 × 500 視窗)1440.5 ms0%1%

#記憶體

靜止狀態,啟動後 3 秒(release runner,1200 × 800 視窗)。Private working set 是該行程獨自使用的記憶體(工作管理員的「記憶體」欄);private bytes 是它已認可(committed)的記憶體;working set 則另外加上它與其他行程共用的系統程式庫分頁(Direct3D、Direct2D、DirectWrite、GPU 驅動程式):

應用程式Private working setPrivate bytesWorking set
Rust 應用程式(nsh init --template rust)~14 MB~60 MB~53–71 MB
TSX 計數器(examples/counter)~15 MB~61 MB~56–71 MB

在 gallery 的導覽流程中,working set 在 Stocks 約為 108–115 MB,進入 Canvas 頁面後約 230 MB,進入 Text View 頁面後約 320–340 MB,而在 Web and video 開啟 WebView2 後約 390–405 MB(其網頁在 WebView2 自己的行程中執行),其中包含刻意快取的內容(解碼後的影像,上限為 128 MB;Text View 頁面的文件)。

#App 大小

nsh init 以每個範本建立的 hello world 應用程式,依 nsh package --target windows 為 x86_64 建置的結果(Rust runner 啟用連結時期最佳化並移除符號,與 Linux 相同)。可執行檔是應用程式的 <Name>.exe;zip 是寫在其目錄旁的 .zip:

應用程式開發語言可執行檔Zip
Hello world(--template rust)Rust15.0 MB6.7 MB
Hello world(--template ts)TSX,基於 QuickJS28.6 MB12.4 MB
Hello world(--template mixed)TSX 與 Rust crate26.3 MB11.5 MB

這些數字的成因:

  • 只為變化產生影格,且只繪製變動之處:由合成器(compositor)調節節奏的 ticker 會在原生動畫、轉場或影格回呼執行期間運作。每個影格會在視窗所保留的像素之上,繪製發生變化的 view 區域(包含其原本位置與目前位置),並且只將這些區域提交給合成器(Stocks:每個影格約 34 個 view)。
  • Canvas 錄製其動畫部分:繪圖內容會被錄製為 Direct2D 命令清單,在任何縮放比例下都保持向量與銳利。當動畫值改變時,只有從第一個讀取動畫值的操作起的後續操作會重新錄製;之前的部分則予以保留(Stocks:22 條脈動走勢線與圖表,每秒約 26 ms 的錄製時間)。
  • 只繪製可見的內容:捲動 view 或裁切 view 中位於可視範圍外的子項目不會納入影格,而沒有自身內容的 view 僅繪製其背景與邊框。

#iOS(UIKit)

在 iOS 模擬器(iPhone 18 Pro Max,執行於上述 MacBook Pro)中量測,使用 release 建置與相同的導覽流程(perf.py --target ios:nsh test --target ios --fps,即 UIKit 所收到的手指觸控)。模擬器以 60 Hz 呈現;在配備 ProMotion 螢幕的 iPhone 上,NSH 的 display link 會以螢幕的 120 Hz 運作(由 nsh package 建置的應用程式與開發主機會透過 CADisableMinimumFrameDurationOnPhone 要求此行為)。各階段為 tick(原生動畫)、drain(套用引擎的批次更新)與 scroll(捲動 view 之後接續的處理:清單、sticky 儲存格、繫結值)。

頁面影格來源FPS影格 p95CPU
Home, scrolling捲動600.1 ms6%
Stocks即時資料、原生迴圈600.3 ms22%
Stocks, scrolling即時資料、捲動600.3 ms25%
Photos, scrolling捲動601.2 ms6%
Lists, scrolling捲動601.1 ms8%
Scrolling, scrolling捲動600.1 ms7%
Navigation, scrolling捲動600.2 ms7%
Form, scrolling捲動600.3 ms7%
Gestures彈簧、脈動圓環600.3 ms3%
Animations原生迴圈600.7 ms6%
Canvas原生迴圈600.4 ms16%
Styles擺動的 transform600.4 ms4%
Tailwind, scrolling捲動600.2 ms7%
Text, scrolling捲動600.1 ms7%
Text View, scrolling捲動600.1 ms6%
Images, scrolling捲動600.1 ms5%
Components, scrolling捲動600.2 ms6%
Overlays, scrolling捲動600.1 ms6%
Menus, scrolling捲動600.2 ms7%
Controls, scrolling捲動、轉場600.2 ms5%
Web and video, scrolling捲動600.1 ms8%
SystemrequestAnimationFrame、計時器600.3 ms4%
Standard APIs, scrolling捲動600.1 ms7%
Storage, scrolling捲動600.1 ms8%

如上所述,FPS 是有東西在移動時的速率:在模擬器的 60 Hz 下,每個影格都在上一個影格之後的一次更新週期出現。沒有任何內容在移動的頁面不會繪製影格,靜止時 CPU 使用率為 1–2%。該行程在 gallery 首頁佔用 40 MB,完成整個導覽流程後約 180 MB(包含刻意快取的影像,以及 Text View 頁面的文件)。

在 iPhone 17 Pro(ProMotion,120 Hz)上,螢幕上同時執行開發主機與一個範例時,動畫頁面會在每次更新時繪製影格:

頁面影格來源FPS影格 p95超過一次更新週期CPU記憶體
Stocks即時資料、原生迴圈1200.2–0.3 ms0%46–70%41–43 MB
Canvas原生迴圈1200.1 ms0%25–36%37–38 MB
Animations原生迴圈1200.2 ms0%3–11%37 MB

Stocks 每秒提交 20 次(每 50 ms 一個 tick),每次在引擎執行緒上花費 1.5 ms 於 JS,以及 1.4 ms 於核心(p95 分別為 2.2 與 2.0 ms),引擎執行緒的忙碌時間占 4–6%。

#量測應用程式

若要以相同方式量測應用程式,請在 --target linux、macos、windows 或 ios 上,使用 --fps 與 -v 執行其測試或步驟:

cargo build --release -p nsh-cli
python3 examples/gallery/tools/perf.py --target macos --only Stocks,Canvas   # the gallery, page by page
echo 'await app.wait(5000)' | nsh -v test App.tsx --target linux --fps        # any app