Chromium のユーザー補助のパフォーマンスを改善

この記事は、Chromium コントリビューターの Ahmed Elwasefi によるものです。Google Summer of Code を通じてコントリビューターになった経緯と、ユーザー補助のパフォーマンスに関する問題を特定して修正した方法について説明しています。

カイロのドイツ大学でコンピュータ エンジニアリングの最終学年を迎えるにあたり、オープンソースに貢献する機会を探すことにしました。Chromium の初心者向けの問題リストを調べ始め、特にユーザー補助に興味を持ちました。指導者を探していたところ、Aaron Leventhal を見つけました。彼の専門知識と助けたいという意欲に触発され、彼とチームを組んでプロジェクトを進めることにしました。このコラボレーションは、Google Summer of Code の経験となり、Chromium ユーザー補助チームで働くことになりました。

Google Summer of Code を無事に終えた後も、パフォーマンスを改善したいという思いから、未解決のスクロールの問題に取り組みました。Google の OpenCollective プログラムから 2 つの助成金を受けたおかげで、このプロジェクトの作業を継続しながら、コードの効率化とパフォーマンスの向上に重点を置いた追加のタスクも引き受けられました。

このブログ投稿では、過去 1 年半にわたる Chromium での私の経験を共有し、特にパフォーマンスの分野で行った技術的な改善について詳しく説明します。

ユーザー補助コードが Chrome のパフォーマンスに与える影響

Chrome のユーザー補助コードは、スクリーン リーダーなどの支援技術がウェブにアクセスするのに役立ちます。ただし、有効にすると、読み込み時間、パフォーマンス、バッテリー駆動時間に影響する可能性があります。そのため、必要ない場合は、パフォーマンスの低下を防ぐためにこのコードは非アクティブのままになります。ユーザーの約 5 ~ 10% はユーザー補助コードを有効にしています。これは、プラットフォームのユーザー補助 API を使用するパスワード マネージャーやウイルス対策ソフトウェアなどのツールが原因でよく見られます。これらのツールは、これらの API を使用してページ コンテンツを操作および変更します(パスワード マネージャーやフォーム フィラーのパスワード フィールドの検索など)。

コア指標の総合的な低下はまだ不明ですが、最近実施された「ユーザー補助の自動無効化」というテスト(ユーザー補助が使用されていないときにユーザー補助を無効にする)では、かなり低下することが示されています。この問題は、Chrome のユーザー補助コードベースの 2 つの主要な領域(レンダラとブラウザ)で大量の計算と通信が行われるため発生します。レンダラは、ウェブ コンテンツとコンテンツの変更に関する情報を収集し、ノード ツリーのユーザー補助プロパティを計算します。変更されたノードはシリアル化され、パイプを介してブラウザ プロセスのメイン UI スレッドに送信されます。このスレッドは、この情報を受け取って同じノードのツリーに逆シリアル化し、最終的にスクリーン リーダーなどのサードパーティの支援技術に適した形式に変換します。

Chromium のユーザー補助機能の改善

次のプロジェクトは、Summer of Code 中に完了し、その後 Google OpenCollective プログラムから資金提供を受けました。

Cache improvement

Chrome には、DOM ツリーをミラーリングするユーザー補助ツリーと呼ばれる特別なデータ構造があります。これは、支援技術がウェブ コンテンツにアクセスできるようにするために使用されます。デバイスがこのツリーの情報が必要になったときに、その情報が準備できていない場合があります。その場合、ブラウザはこれらのリクエストを後でスケジュールする必要があります。

以前は、このスケジューリングはクロージャーと呼ばれるメソッドを使用して処理されていました。このメソッドでは、コールバックをキューに格納していました。このアプローチでは、クロージャーの処理方法が原因で追加の作業が発生しました。

この問題を改善するため、列挙型を使用するシステムに切り替えました。各タスクには特定の列挙型値が割り当てられ、ユーザー補助ツリーが準備されると、そのタスクの正しいメソッドが呼び出されます。この変更により、コードの理解が容易になり、パフォーマンスが 20% 以上向上しました。

ランタイム パフォーマンス テストのグラフ。
複数のパフォーマンス テストの実行時間のグラフ。すべてのテストで約 20% の顕著な低下が見られます。

スクロール パフォーマンスの問題の特定と修正

次に、境界ボックスのシリアル化をオフにした場合のパフォーマンスの向上を確認しました。境界ボックスは、ウェブページ上の要素の位置とサイズです。幅、高さ、親要素に対する位置などの詳細が含まれます。

これをテストするために、境界ボックスを処理するコードを一時的に削除し、パフォーマンス テストを実行して影響を確認しました。focus-links.html というテストでは、約 1, 618% という大幅な改善が見られました。この発見が、今後の作業の基盤となりました。

遅いテストの調査

バウンディング ボックスでその特定のテストが遅い理由を調査しました。このテストでは、複数のリンクに次々とフォーカスを当てただけです。したがって、主な問題は、要素のフォーカスまたはフォーカス アクションで発生したスクロールのいずれかである必要があります。これをテストするために、パフォーマンス テストの focus() 呼び出しに {preventScroll: true} を追加し、スクロールを停止しました。

スクロールを無効にすると、境界ボックスの計算が有効な場合のテスト時間が 1.2 ミリ秒に短縮されました。スクロールが実際の問題であることが判明しました。

スクロールが無効になっている場合のテスト結果。
スクロールが無効になっているか、境界ボックスのシリアル化が削除されている場合、フォーカスリンク テストの実行時間が 20 ms から 1.1 ms に短縮されます。

focus-links テストを再現するために、scroll-in-page.html という新しいテストを作成しました。このテストでは、フォーカスを使用する代わりに、scrollIntoView() を使用して要素をスクロールします。ボーダーボックスの計算ありとなし、スムーズ スクロールとインスタント スクロールの両方をテストしました。

新しいテストのテスト結果。
即時スクロールでのスクロール処理時間は 65 ミリ秒ですが、スムーズ スクロールでは 123 ミリ秒かかります。

結果は、インスタント スクロールと境界ボックスを使用すると、プロセスに約 66 ミリ秒かかることを示しています。スムーズ スクロールはさらに遅く、124 ミリ秒程度でした。境界ボックスをオフにすると、イベントがトリガーされなかったため、時間はほとんどかかりません。

問題はわかったが、なぜ起きていたのか?

スクロールがユーザー補助のシリアル化の遅延の原因であることはわかいましたが、その理由を特定する必要がありました。これを分析するために、perfpprof という 2 つのツールを使用して、ブラウザ プロセスで行われた作業を分類しました。これらのツールは、C++ でプロファイリングによく使用されます。次のグラフは、興味深い部分のスニペットを示しています。

プロファイリングされたスクロール テストのグラフ。
スクロール テストのプロファイリングから生成されたグラフ。時間のほとんどが、Unserialize という関数と IsChildOfLeaf という関数の呼び出しに費やされていることを示しています。

調査の結果、問題はシリアル化解除コード自体ではなく、その呼び出し頻度にあることが判明しました。これを理解するには、Chromium でのユーザー補助機能の更新の仕組みを理解する必要があります。更新は個別に送信されません。代わりに、すべてのプロパティを保存する AXObjectCache という一元化された場所があります。ノードが変更されると、さまざまなメソッドがキャッシュに通知し、後でシリアル化できるようにそのノードをダーティとしてマークします。次に、変更されていないプロパティを含む、変更済みノートのすべてのプロパティがシリアル化され、ブラウザに送信されます。この設計では、更新パスが 1 つしかないため、コードが簡素化され、複雑さが軽減されますが、スクロールなど、急速な「dirty としてマーク」イベントが発生すると、速度が低下します。変更されるのは scrollX 値と scrollY 値のみですが、残りのプロパティは毎回一緒にシリアル化されます。更新レートは 1 秒あたり 20 回を超えました。

境界ボックスのシリアル化では、境界ボックスの詳細のみを送信する高速なシリアル化パスを使用するため、他のプロパティに影響を与えることなく迅速に更新できます。この方法では、境界ボックスの変更を効率的に処理します。

スクロールの修正

解決策は明らかでした。境界ボックスのシリアル化に現在のスクロール オフセットを含めるのです。これにより、スクロール アップデートが高速パスで処理され、不要な遅延なしでパフォーマンスが向上します。スクロール オフセットを境界ボックス データと共にパックすることで、プロセスを最適化し、よりスムーズで効率的な更新を実現し、ユーザー補助をオンにしているユーザーにとってぎくしゃくした操作感を軽減します。修正の実装後のスクロール テストでの改善は最大 825% です。

コードの簡素化

この期間中、Onion Soup というプロジェクトの一環としてコード品質に重点を置きました。このプロジェクトでは、レイヤ間で不要に広がっているコードを減らしたり削除したりすることで、コードを簡素化しています。

最初のプロジェクトでは、ユーザー補助データがレンダラからブラウザにシリアル化される方法を効率化することを目的としていました。以前は、データが宛先に到達する前に追加のレイヤを通過する必要があり、不要な複雑さが追加されていました。データを直接送信できるようにすることで、仲介業者を排除し、このプロセスを簡素化しました。

また、レイアウトの完了時にトリガーされるイベントなど、システムで不要な処理を引き起こしていた古いイベントを特定して削除しました。これらの機能は、より効率的なソリューションに置き換えられました。

また、その他の小さな改善も行いました。残念ながら、これらの変更によるパフォーマンスの向上は記録されませんでしたが、コードは以前よりも明確で自己文書化されていることをお知らせいたします。これにより、今後のパフォーマンス向上につながります。実際の変更内容は、私の gerrit プロフィールで確認できます。

まとめ

Chromium ユーザー補助チームとの連携は、やりがいのある経験でした。スクロール パフォーマンスの最適化からコードベースの簡素化まで、さまざまな課題に取り組むことで、このような大規模なプロジェクトでの開発について深く理解し、プロファイリングに重要なツールを学びました。また、すべてのユーザーを対象としたウェブを作成するために、ユーザー補助がどれほど重要であるかを学びました。改善により、ユーザー補助技術を利用しているユーザーのユーザー エクスペリエンスが向上するだけでなく、ブラウザの全体的なパフォーマンスと効率性が向上します。

パフォーマンスの結果は素晴らしいものでした。たとえば、タスクのスケジュールに列挙型を使用するように切り替えることで、パフォーマンスが20%以上向上しました。また、スクロールの修正により、スクロール テストの時間が最大で 825% 短縮されました。コードの簡素化により、コードの明確さとメンテナンス性が向上しただけでなく、将来の機能強化の道も開かれました。

1 年間にわたるサポートと指導をしてくれた Stefan Zager、Chris Harrelson、Mason Freed に感謝します。特に、この機会をいただけなかったであろう Aaron Leventhal に感謝します。また、Tab Atkins-Bittner 氏と GSoC チームのサポートにも感謝いたします。

有意義なプロジェクトに貢献し、スキルを磨きたいと考えている方には、Chromium への参加を強くおすすめします。これは学習に最適な方法であり、Google Summer of Code などのプログラムは、この取り組みの出発点として最適です。