2
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

ターミナルアプリの描画・入力・リソースを定量評価するベンチマークツール TerminalBench

2
Posted at

Noa というターミナルアプリを自作しています。開発していると、避けて通れない問いが 2 つ出てきます。この変更で描画は速くなったのか(遅くなっていないか)、そして競合のターミナルアプリと比べて今どの位置にいるのか。どちらも体感では答えられない。数値で答えるために書いたのが TerminalBench (tbench) です。

TL;DR

  • シェルの起動と、ターミナルエミュレータの描画・入力・リソースを 1 コマンドで計測し、他のターミナルと横並びで比較できる Rust 製の単一バイナリです。
  • 一部の値は近似(proxy)で、ツールが JSON の notes に但し書きを出します。
  • まずは bin/tbench run してから bin/tbench show

※ 名前は似ていますが、LLM エージェント評価の Terminal-Bench(laude-institute)とは別のプロジェクトです。

TerminalBench とは

tbench は、シェルとターミナルエミュレータの速さをまとめて測るベンチマークランナーです。実行時の追加依存はなく、cargo build --release で 1 個のバイナリになります。

測定対象は大きく 4 系統です。シェル起動(shell.startup / cmd.overhead)、描画(render.throughput / scroll.proxy / fire.fps)、入力(input.latency)、リソース(resource.mem / resource.cpu)。このうちシェル起動系だけが --shells zsh,bash の各シェルで計測され、残りはシェル非依存("shell": null)として記録されます。ただし input.latency はエミュレータ自体ではなく PTY 往復を測る近似です(詳しくは後述)。

出力は 1 個の JSON ドキュメント。標準出力に流しつつ results/ にも保存し、run の後は人間向けサマリを stderr に出します。stdout はそのままスクリプトにパイプでき、目視でも確認できます。

何が測れるか — メトリクス 8 種

tbench list でも一覧できますが、全体像は次の表にまとめました。8 種はいずれも先述の 4 系統に対応します。

Metric Unit Shell依存 Variants 測るもの
shell.startup ms あり {shell} -i -c exit の実時間。rc ファイル込みの対話起動時間
cmd.overhead μs あり {shell} -c true の実時間。プロセス spawn + builtin dispatch(組み込みコマンドの解決)
render.throughput MB/s, lines/s なし plain / ansi / cjk 固定フィクスチャを書き込んだときの producer 側の完了レート
scroll.proxy ms なし 大きな cat producer が終了するまでの実時間(proxy)
input.latency ms なし idle / loaded PTY 往復。fresh pty 上の cat にバイトを書き、エコーが返るまで
resource.mem MB なし idle / load ターミナルエミュレータ自身の GUI プロセスの RSS
resource.cpu % なし idle / load 同 GUI プロセスの %CPU
fire.fps fps なし <columns>x<rows> DOOM-fire ストリームの producer 完了フレーム/秒

各メトリクスはデフォルト 20 試行(--runs)で median / p95 / stddev を出します。ただし resource.* は 1 variant あたり最大 5 サンプルの点計測、fire.fps は時間ベース(FIRE_SECS 秒、デフォルト 5 秒)で試行数は min(--runs, 5) に丸められます。ライブプロセスは 5 サンプルで十分ですし、fire は 20 回だとスイートが長くなりすぎるからです。

shell.startup-i がポイントで、rc ファイルをロードした状態の起動時間を測ります。「プラグインマネージャを入れ替えたら起動がどれだけ変わったか」を見るのはこの指標です。

インストールと基本的な使い方

動かすには Rust ツールチェイン(cargo)が要ります。ビルド済みバイナリの配布はなく、bin/tbench がソースからビルドして実行します。

# 取得してディレクトリへ
git clone https://github.com/simota/TerminalBench && cd TerminalBench

# 全シナリオ、デフォルトシェル(zsh,bash)、各 20 試行
bin/tbench run

# シナリオを 1 つだけ
bin/tbench run --scenario shell-startup

# シェルと試行数を指定(シェル依存シナリオのみ効く)
bin/tbench run --scenario shell-startup --shells zsh,bash --runs 5

# 利用可能なシナリオ一覧(name / metric / unit / scope / description)
bin/tbench list

# 直近レポートの人間向けリキャップ
bin/tbench show

run(や show)を叩くと、stderr にこんなリキャップが出ます。

Terminal:        App-A
Shell
Metric                Median     P95  Stddev  Runs  Unit  Dir
--------------------  ------  ------  ------  ----  ----  ---
shell.startup (zsh)   480.54  510.49   17.87     5    ms    ↓
shell.startup (bash)    6.22    7.08    0.44     5    ms    ↓

--app で計測対象の識別名を付けられます。同じエミュレータの 2 ビルドを区別したいときに使います。

bin/tbench run --app 'App A' --output results/app-a.json

listshow は読み取り専用でロックを取らないので、run 実行中に横から叩いても安全です。

エミュレータを比較する — compare / orchestrate / HTML レポート

比べたいエミュレータそれぞれの中で bin/tbench run を回し、出てきた JSON を compare に渡します。各レポートは meta.terminal$TERM_PROGRAM 由来)でタグ付けされます。

# 2 つ以上のファイルを Side-by-side の Markdown 表に
bin/tbench compare results/app-a.json results/app-b.json results/app-c.json

# 自己完結の HTML チャートレポート(外部 CDN/フォント/画像なし)
bin/tbench compare results/a.json results/b.json --format html

# CI 回帰ゲート: ちょうど 2 ファイル(baseline, latest)、回帰で非ゼロ終了
bin/tbench compare results/baseline.json results/latest.json --threshold 10%

行は metric [variant] (shell) (unit)、列は各ファイルの meta.terminal。セルは median で、行内のベスト値が太字になります。方向も考慮していて、render.throughputfire.fps は higher-is-better、他は lower-is-better です。レポート同士が厳密に比較可能か(同じ環境・条件か)は bin/tbench-validate で検証でき、突き合わせ可否を COMPARABLE 判定として返します。

以下は手元の Mac での一例です(2026 年 7 月時点)。Noa の中で bin/tbench run --scenario shell-startup --runs 5 を 2 回実行し(--app タグで Noa-A / Noa-B と区別)、compare にかけた結果です。

Metric Noa-A Noa-B
shell.startup (zsh) (ms) 480.54 472.97
shell.startup (bash) (ms) 6.22 5.67

Detail (p95 / stddev)

Metric Noa-A p95 / stddev Noa-B p95 / stddev
shell.startup (zsh) (ms) 510.49 / 17.87 488.26 / 11.63
shell.startup (bash) (ms) 7.08 / 0.44 5.81 / 0.08

同一環境の 2 回実行なので、太字のベスト値の差は実力差ではなく run 間のばらつきです。この環境では zsh がプラグイン込みで 500ms 前後、bash が 6ms 前後という桁違いも一目で分かります。

手でエミュレータを 1 つずつ開いて回すのは、忙しいと真っ先にサボります。かといって同時に 2 つ走らせると、計測が互いに干渉します(次の節で触れます)。これを構造的に防ぐのが orchestrate です(macOS 限定)。

bin/tbench orchestrate --apps Noa,AppB,AppC \
  --scenario shell-startup --runs 20 \
  --output-dir results/orchestrate-$(date +%Y%m%d) \
  --format html

orchestrate は各アプリの新規ウィンドウで tbench run を起動し、JSON が出るまで待ってから次へ進みます。通常経路では、2 つの run が重なることはない。ただしタイムアウトした run だけは、前のウィンドウが残ったまま次へ進むことがあります。全アプリを回し終えたら compare まで自動でかけます。アダプタ方式で、自作の noa を含む主要なターミナルアプリ 4 つに対応しています(対応名は README に)。AppleScript を使うアダプタでは、初回に macOS の Automation 許可プロンプトが出ます。すでに起動中のアプリは終了させません。勝手にセッションを閉じるほうが害が大きいからです。

HTML レポートはインライン SVG + バニラ JS だけの 1 ファイルで、メトリクス+variant+unit ごとに dot-and-interval チャートを出します。「Wins per entity」集計、方向を考慮した competition rank、rank 1 への ★ best バッジ、ツールチップ付きです。

測定品質を保つ仕組み

開発中、計測値が実態とずれるケースに何度か当たりました。idle のはずのターミナルで CPU が 243% と記録されたり、描画スループットが 70 MB/s から 13 MB/s に落ちたり。前者は macOS の CPU 使用率が直近の負荷を含む移動平均であること、後者は 2 つの計測を同時に走らせたことが原因でした。

run にはこうしたずれを防ぐ仕組みが入っています。計測の間に休止を挟む、別の計測が走っていれば完了を待つ、ウォームアップ分を捨てる、外れ値を除いてから統計を取る、数値が安定するまで試行数を自動で増やす、といった内容です。詳しいパラメータやアルゴリズムは README にあります。

変更が本物の改善かどうかの判定も tbench 側でできます。両方のレポートを --keep-samples 付きで取り --threshold で比べると、compare が 2 つの分布を統計的に比較し、差がばらつきの範囲なら「有意ではない」と報告します(Mann-Whitney U 検定)。サンプルがない場合は従来どおり閾値のみの判定です。

この数値、どこまで信じていいのか

tbench の数値のいくつかは**近似(proxy)**で、実測そのものではありません。どこが近似なのかは、ツール自身が JSON の notes に書き出します。

たとえば入力遅延(input.latency)。これはキーを押してから画面に文字が出るまでの本当の遅延ではなく、疑似端末(PTY)にバイトを書いて返ってくるまでの往復時間です。本物のキー入力から画面表示までを測るには GUI の自動操作が要るので、そこはスコープ外にしています。

描画系(render.throughput など)も性格は同じです。測っているのは「ターミナルへの書き込みが完了したレート」で、「画面に実際に描かれたフレームレート」ではありません。書き込みが終わっても、GUI が本当に何 fps で描けているかまでは踏み込んでいない。

残りの近似(スクロール、同期描画の検出)も同じで、それぞれ何が近似なのかは docs にまとめてあります。

環境によって走らないものもあります。非 macOS では resource.* はスキップされ、orchestrate はそもそもエラーで止まります。制御端末(controlling tty)がない CI などでは、描画・スクロール・fire 系もスキップされます。そうした環境では、端末名やマシン種別を TBENCH_* の環境変数で補えます。ただしこれはメタデータを補うだけで、足りない tty を用意するわけではありません。

TBENCH_TERMINAL_ID='ci-terminal 1.0' \
TBENCH_MACHINE_MODEL='ci-runner-class-a' \
TBENCH_POWER_SOURCE=ac \
  bin/tbench run --output results/ci-report.json

cjk variant の MB/s は少しクセがあります。全角日本語や絵文字は UTF-8 で 1 文字 3〜4 バイトなので、同じ見た目の幅でも plain/ansi より多くのバイトを食います。variant 間の比較には linesps のほうがフェアです。

まとめ

tbench の数値は、単体で眺める絶対値ではなく、比べるための相対値です。一部は近似なので、1 つの数字そのものに意味を求めるより、同じ環境・同じ条件で並べたときの差に意味があります。変更の前後で比べれば回帰が分かり(compare --threshold)、他のターミナルアプリと並べれば立ち位置が分かる(orchestrate)。自分も Noa の開発では、そういう使い方をしています。

まずは自分のターミナルの中で動かしてみてください。

bin/tbench run
bin/tbench show

リポジトリと公開サイトはこちら。

ターミナルアプリを開発している人も、使っているターミナルの実力が気になる人も、体感を数値で答え合わせしてみてください。

2
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
2
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?