NTT DATA TECH
📑

VS Code 拡張機能の Webview での描画性能をテトリスで検証してみた

に公開

1. はじめに

突然ですが、VS Codeの拡張機能を作ったことはあるでしょうか。

今まで私は、何か難しそうだという理由から、全く作ろうと考えていませんでしたが、生成AIの進歩によってお手軽に作れることを知って、最近いくつか試しに作っています。

今回は、「VS Code の Webview でどこまで動くのか?」 という疑問から、VS Code 拡張機能の描画性能について着目し、ごく簡単にではありますが、Web ブラウザの場合との比較検証をテトリスを動かすことで実施してみました。

また、VS Code標準のUIではなく、描画の自由度がより高いWebview[1]を使うことにしました。テトリスの描画には Canvas 2D API を利用しています。

ゲームに限らず、コードのビジュアライザやリアルタイムダッシュボードなど、Canvas を活用したリッチな UI を VS Code 拡張に組み込みたい方にも参考になれば幸いです。

2. 動機

VS Code拡張機能を作っていると、様々なことが実現できることに気づき、ではどこまでならできるのだろうか?と気になりました。

すぐに思いついたのはゲームです。
ただ、テトリスとかできたら面白いのでは…と思い調べてみると、すでにネット上では実現されている方がいました。

そのため、テトリスの実装自体を目的にするのではなく、最近興味を持ち始めているフロントエンドや非機能領域に関連した、描画性能に着目してみようと思いました。

3. 設計方針

ソースコードの実装は完全に生成AI(GitHub CopilotでClaude Sonnet 4.6を使っています)に任せました。
ここではどのような設計方針だったかを説明します。

比較対象の選定

単に性能を計測するだけでは、評価しづらいため、比較対象を用意しようと考えました。

簡単に比較できるであろう、Web ブラウザを比較対象としました。

メトリクス設計

ブラウザ標準の時刻計測 API を使って各種データをタイムライン形式で記録します。計測データはゲーム終了時に JSON ログとして保存します。ログには環境識別子や計測開始日時なども含まれます。

フィールド 内容 取得タイミング
frameTime ゲームロジック処理 + Canvas 描画の合計時間(ms) 1フレームごとの瞬間値
memoryMB JS (JavaScript) ヒープメモリ使用量(MB) 1フレームごとのスナップショット

時刻計測には performance.now() を利用しました。これは Web ブラウザと VS Code の Webview の双方で利用できる標準 API であり、環境非依存の計測が実現できます。

一方、メモリ計測に用いた performance.memory[2] は Chromium 系(Google Chrome や VS Code が使うブラウザエンジン)限定の非標準 API であり、参考値として扱います。

なお、代替として Performance.measureUserAgentSpecificMemory()[3] が提供されていますが、このAPIは結果をすぐ返さない非同期 API のため、フレームごとの瞬間値を記録する今回の計測ループには組み込みにくいことから、今回は performance.memory を採用しました。

パッケージ構成

今回は拡張機能だけではなく、比較のためにWeb ブラウザでも動かそうと思いました。

比較をしやすくするために、極力共通化を図り以下のようなパッケージ構成にしました。

packages/
  core/              ゲームロジック(DOM・Canvas 依存なし)
  runtime/           描画・ループ・入力・計測(共通)
  web/               Web 版エントリポイント
  vscode-extension/  VS Code 拡張版エントリポイント
  • core はミノ(テトリスで落下する4マスのブロックピース)の落下・衝突判定・ライン消去・スコア計算をすべて数値と配列の計算だけで完結させており、描画や環境固有の API に一切触れない純粋な実装です。
  • runtime は Web ブラウザと VS Code Webview の両方で同一コードが動きます。フレームごとに「計測開始 → ゲーム更新 → 描画 → 計測終了」の順で処理され、この区間の経過時間をフレーム処理時間(frameTime)として記録します。
  • web / vscode-extension はエントリポイントだけが環境ごとに異なります。

ポイントは「ゲームロジック・描画・ループ・計測コード」を共通化し、エントリポイントだけを実行環境ごとに分けた点です。これにより、計測値の差からアプリコードの差を極力なくして、実行環境(Web ブラウザ vs VS Code Webview)由来の差に近づけることができます。

ボット(自動操作)

計測の際、最初は人力でゲーム画面を操作することを考えていました。

ですが、ある程度の量のデータを取得するためには、それなりに集中してプレイする必要があります。

流石に何回もプレイするのは体力的に厳しく、また、ゲームの進行度やライン消去頻度が変わって、公平な比較が難しいと判断し、ボットを導入することにしました。

最初は適当に配置させようとしたのですが、同じ場所に積み上げるだけですぐにゲームオーバーになってしまいました。

そこで、ある程度安定して動かせるアルゴリズムを利用することにしました。

生成されたソースコードについてGitHub Copilotで聞いてみると、集計高さ・消去ライン数などを重みづけスコアで評価するヒューリスティックなアルゴリズムを採用したとのことです。

これにより、どの環境でも同じ操作パターンで 180 秒程度なら安定してゲームを動かし続けることができ、環境差以外の変動要因を減らすことができます。

4. 計測方法

計測項目

項目 指標
フレーム処理時間(frameTime 平均・中央値・最大・p95・p99・標準偏差
JS ヒープメモリ使用量(memoryMB 平均・最大
  • frameTime: 値が小さいほど描画が軽量・高速です。今回は60fpsのディスプレイを用いているため、16.67ms を超えるとフレームドロップが発生します。最大・p99 が大きい場合は瞬間的なスパイクがあります。
  • memoryMB: 値が小さいほどメモリ消費が少ないです。大きい場合は長時間実行でガベージコレクション(GC)によるスパイクにつながる可能性があります。

実行環境

ソフトウェア バージョン
VS Code 1.112.0
Electron 39
Chrome(Webview) 142
Chrome(ブラウザ実行) 122

計測手順

  1. ボット(自動操作)を有効にして 180 秒間ゲームを動かす
  2. ゲーム終了時に JSON ログを保存
  3. 比較した環境は以下の 4 パターン
パターン 環境 起動方法
A VS Code / Windows ネイティブ 拡張機能登録して起動
B VS Code / WSL 接続 拡張機能登録して起動
C VS Code / WSL 接続 F5 デバッグ実行
D Chrome ブラウザ HTML ファイルを直接開く

動作の様子

※ アップロード可能なファイルサイズにするため、フレームレートを20fpsに落としてGIFに変換しています

VS Code版 Web版
tetris_web tetris_vscode

5. 計測結果

フレーム処理時間

項目 A: VS Code Win
(拡張登録)
B: VS Code WSL
(拡張登録)
C: VS Code WSL
(デバッグ実行)
D: Web ブラウザ
平均 (ms) 0.255 0.180 0.342 0.310
中央値 (ms) 0.200 0.200 0.300 0.300
最大値 (ms) 4.3 3.2 12.0 ⚠️ 4.8
p95 (ms) 0.500 0.300 0.700 0.500
p99 (ms) 0.900 0.400 1.100 0.800
標準偏差 (ms) 0.161 0.116 0.224 0.151
60fps 超過フレーム 0 (0%) 0 (0%) 0 (0%) 0 (0%)

全環境で 60fps の閾値(16.67ms)を超えたフレームは 0 件でした。B(VS Code WSL 拡張登録)が平均・p95・p99・標準偏差のすべてでトップの値を示しています。

C(デバッグ実行)のみ最大値が 12ms と他環境の 2〜3 倍になっています。デバッガ接続やソースマップ処理など、デバッグ実行特有のオーバーヘッドが影響した可能性があります。拡張機能の性能計測を目的とする場面では「デバッグ実行」は使わない方がよいでしょう。

意外なことに、B が A(Windows)より安定していましたが、原因は特定できませんでした。

メモリ使用量

項目 A: VS Code Win
(拡張登録)
B: VS Code WSL
(拡張登録)
C: VS Code WSL
(デバッグ実行)
D: Web ブラウザ
平均 (MB) 2.38 2.32 2.37 9.50
最大 (MB) 3.40 3.40 3.40 9.50

Web ブラウザ(Chrome)の結果を見ると、平均値と最大値が同一であることから、全フレームで 9.5 MB 固定となっています。
これは後で調査してわかったのですが、計測に用いた performance.memory API の処理の中で、セキュリティ対策として、攻撃者がメモリ操作の影響を精密に観測できないよう、値をバケット(区間)に丸めているからです。

Chromiumのソースコード[4]によると、最小バケットの境界値は 10,000,000 バイトに設定されています。つまり実際のヒープ使用量が 10MB 未満であれば一律この値が返されます。この値をアプリ側では 1024×1024 で MB 変換したうえで小数第1位に丸めており、10,000,000 / 1,048,576 = 9.5367… → 9.5 MB となります。Chrome は全フレームで同一の値(9.5)が返され続けるため、平均値も 9.5 ちょうどとなり、表では小数第2位まで揃えた 9.50 と表示されています。

そのため計測してみたはいいものの、環境間の直接比較はできないことがわかりました。

一方で、VS Code の Webview でも Chromium エンジンを利用しており、ソースコードの該当部分も同一だということを確認したのです[5]が、VS Code Webview 側ではフレームごとに変動する値が3環境とも計測されています。
なぜ値が変動するのかは調査しましたが、原因は特定できませんでした。

6. 結論

全 4 環境でフレーム処理時間が60fps の閾値 16.67ms を超えたフレームは 1 件もありませんでした(すべて 0%)。最大フレーム時間でさえ、C のデバッグ実行を除けば 5ms 以下に収まっており、閾値を大きく下回っています。

したがって、「今回のテトリス程度の 2D 描画負荷では、VS Code Webview でも Canvas ゲームは問題なく動く」 というのが実測による結論となります。

7. おわりに

今回の計測で「テトリス程度の 2D 描画負荷であれば、VS Code の Webview でも Canvas ゲームは問題なく動く」ことが実測値で示せました。

VS Code 拡張 × Webview(Canvas)の応用先はゲームに限りません。

  • コードの依存関係グラフをインタラクティブに可視化する
  • データ構造のアニメーション付きビジュアライザ
  • テスト結果のリアルタイムダッシュボード

といった用途でも、性能面での懸念は(少なくともテトリスレベルの描画なら)不要だという実感が得られました。

この記事をきっかけに、みなさんのちょっと凝った拡張機能を作成しようという気持ちの後押しになりましたら幸いです。

脚注
  1. VS Code Webview API(公式ドキュメント) ↩︎

  2. performance.memory(MDN) ↩︎

  3. Performance.measureUserAgentSpecificMemory()(MDN) ↩︎

  4. memory_info.cc(Chromium v122 ソースコード) ↩︎

  5. memory_info.cc(Chromium v142 ソースコード) ↩︎

NTT DATA TECH
NTT DATA TECH
設定によりコメント欄が無効化されています