デベロッパーツールを超えて：アセンブリレベルでのWebアプリのプロファイリング

![preview.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3486144/49255eed-d14b-e0fa-8c0c-979a0526dffc.jpeg)
Leaning Technologiesでは、ブラウザのパフォーマンスを常に向上させることを目指し、パフォーマンス最適化は開発者にとって重要な懸案事項となっています。[CheerpX](https://leaningtech.com/cheerpx-for-flash/)や[CheerpJ](https://leaningtech.com/cheerpj/)などの画期的な技術のおかげで、ブラウザ環境内で以前は考えられなかったことが実現可能になりました。このような高度なプロジェクトに取り組む中で、Chromeの組み込みデベロッパーツールのような従来のプロファイリング手法の制約に直面しました。その結果、内部のChromeトレースに基づいたカスタムのプロファイリングワークフローに数年間にわたって頼っていましたが、改善の余地がありました。

私（Elisabeth Panholzer）は最近Leaning Technologiesのインターンとして参加し、主要なプロジェクトとして既存のプロファイリングワークフローを完全に見直し、より効率的で特定のニーズに適したものにすることが任されました。

このプロジェクトは、信じられないほどの学びの経験だけでなく、非常にやりがいのあるものでもありました。過去6ヶ月間に開発した解決策を共有し、途中で発見し学んだことについて詳しく説明することを楽しみにしています。結果として、ブラウザ内でWebアプリケーションを信頼性の高い直感的な方法でプロファイリングできるようになり、linux-perfをベースにカスタムスクリプトを組み合わせたものが完成しました。

# Webアプリケーションのプロファイリングと従来の手法の制限
インターンシップの初めには、プロファイリングについてはあまり具体的な理解がありませんでしたし、特にLeaning Technologiesで開発しているような複雑なアプリケーションのプロファイリングに関してはほとんど知識がありませんでした。プロファイリングは、実行中のアプリケーションのパフォーマンスを計測する高度な動的プログラム解析の形式です。収集した情報は、プログラムの最適化やパフォーマンスエンジニアリングの支援に役立ちます。アプリケーションをプロファイリングするために利用できるさまざまなツールがあります。例えば、ウェブアプリケーション向けのGoogle Chromeの組み込みデベロッパーツールや、後で詳細に議論するLinux Performance Eventsなどがあります。

Chromeのデベロッパーツールは、ブラウザ内のアプリケーションのプロファイリングに広く知られており、広く使用されているツールです。これにより、ウェブサイトのアクティビティを記録し、可視化されたプロファイリングレポートを得ることができます。レポートには、さまざまなグラフとタイムラインが含まれており、CPUの負荷が異なるタスクにどのように分散されているかを示します。また、ウェブサイトのレンダリング進行状況やネットワークリクエストのタイムラインなども表示されます。レポートの中でもおそらく最も興味深いのは、CPUフレームチャートです。これはプログラムのスタックトレースをグラフィカルに表現したもので、どの関数やメソッドが最も時間を消費しているかを特定し、最適化が必要な箇所を特定するのに役立ちます。

しかしここで、デベロッパーツールは限界に達します。CheerpXのような高度なツールをブラウザでプロファイリングする際、正確なプロファイリングレポートを得ることが目標です。残念ながら、デベロッパーツールを使用すると、WebAssemblyコードのコンパイル時間がわずかに遅くなることに気付きました。これは、プロファイリングツール自体が実行時間を妨げるため、最適化が必要なプログラムの具体的な箇所を特定する際に課題となります。

しかも、デベロッパーツールは実行パフォーマンスに悪影響を及ぼすだけでなく、ネイティブコードに関する情報を得ることもできません。デベロッパーツール内ではほとんどの関数に対してソースコード情報が利用できますが、実際のネイティブコードに関する情報は提供されません。ここでの根本的な問題を理解するためには、CheerpXのコンパイルパイプラインがどのように機能しているかを少し深く探求し、ブラウザがコンパイルされたコードを管理する方法を見直す必要があります。

# CheerpX コンパイル パイプラインとプロファイリング レポートの欠落情報
![compilation2.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3486144/92b9f766-874c-a931-fd21-44f602f8833f.jpeg)

CheerpXは、ブラウザでバイナリコードを安全に実行するための堅牢で拡張性のある技術です。C++で書かれ、WebAssemblyにコンパイルされています。これは[WebVM](https://leaningtech.com/webvm/)のコアを構成し、HTML5/WebAssemblyを使用して完全にクライアントサイドで実行されるサーバーレスのLinux仮想環境を提供します。

CheerpXは、インタプリタとJITコンパイラからなる2段階の実行エンジンを採用しています。動作は次のようになります：ネイティブのx86機械語はまずインタプリタによって処理され、一度に1つの命令を実行して将来の最適化のためのデータを収集します。このデータにより、制御フローグラフが構築され、実行された関数のホットセクションが特定されます。制御フローグラフのコードトレースがホットになると、WebAssemblyにコンパイルされます。これが実行エンジンの2番目の部分です。

生成されたWebAssemblyコードは、ブラウザのWebAssemblyランタイムによって検証されます。コードが検証されると、ブラウザはさらなるコンパイルを実行し、WebAssemblyコードを再びネイティブコードに変換してブラウザによって直接実行されます。実行されるネイティブコードがプロファイリングレポートで観察および分析される内容でもあります。これにより、従来のデベロッパーツールや他のプロファイリングプログラムを使用した従来のプロファイリング手法に関する根本的な問題が浮き彫りになります。ブラウザで実行されるコードは観察できるが、CheerpXで実行される元のコードとは異なるため、プロファイリングレポートに示される情報は実際のコードそのものではなく、代理情報に過ぎません。

この問題を解決するため、カスタムのプロファイリングワークフローの目標は、元のネイティブx86コードをプロファイリングレポートで見られるネイティブコードにマッピングすることです。これを実現するための最初のステップは、異なるプロファイリングツールであるLinux Performance Eventsにより理解を深めることでした。

# Linux パフォーマンス イベント
![blog_post6.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3486144/fb38334b-7609-2235-f609-ef7e0eaed445.png)
Linux Performance Events、またはlinux perfとして知られるものは、Linuxカーネルに組み込まれた強力なプロファイリングおよびパフォーマンス分析ツールです。これにより、CPU利用率、メモリ使用量、ディスクI/Oなど、システムのさまざまな側面の詳細な情報を収集できます。Linux perfはハードウェアパフォーマンスカウンターやソフトウェアイベントを活用して低レベルでデータを収集し、コードの実行を詳細に分析し、パフォーマンスのボトルネックを特定し、システムのパフォーマンスを最適化することができます。Linux perfは、Google ChromeのJavaScriptエンジンV8と組み合わせてWebアプリケーションのプロファイリングにも使用できます。V8はlinux perfをネイティブでサポートしています。

インターンシップの初期に直面した課題の1つは、linux perfとGoogle Chromeのスムーズな統合を確保することでした。linux perfは、linux-toolsパッケージに含まれているため、ほとんどのLinuxマシンに事前にインストールされています。最小限の努力で、基本的なC++アプリケーションのプロファイリングレポートを生成することができました。ツールにより慣れてきた後、次のステップはブラウザ内のアプリケーションをプロファイリングすることでした。公式のV8ドキュメントにはlinux perfを使用したプロファイリングについての簡単なセクションがありますが、役立つ情報の大部分は主にGoogle/V8の開発者を対象としたさまざまなGoogleのウェブサイトで見つけることができます。さまざまなドキュメントを読みながら、作業環境に適したセットアップを特定するのに想定以上に時間がかかりました。

すべてがセットアップされて動作するようになった後は、次のステップとしてプロファイリングワークフローの自動化を行いました。現在、ブラウザ内のアプリケーションをlinux perfでプロファイリングするには、かなり手間のかかるさまざまなステップが必要です。カスタムのV8フラグを使用してプロファイリングを有効にするためにコマンドラインからChromeを起動し、linux perfを使用してChromeのレンダラープロセスIDでイベントを記録し、生成されたレポートを後処理する必要があります。私はPythonを使ってこれらのステップを自動化するカスタムスクリプトを作成しました。その結果、簡単に実行できるワークフローが得られました。

このカスタムプロファイリングスクリプトにより、初期のChromeのデベロッパーツールに関する問題が解決しました。プロファイリングをサポートするためのV8フラグは、ブラウザ内の私たちの技術の実行時間に影響を与えることはありません。次のプロジェクトのステップは、プロファイリングレポートでネイテイブコードを観察する方法を見つけることでした。

# ソースコードとバイナリファイルの探索
linux perfによって生成されたパフォーマンスレポートを探索することは、簡単なプロセスです。コマンドラインからレポートを開くことで、統合されたTUI（テキストユーザーインターフェース）を使用してデータを調査できます。実行時間が長い関数を素早く特定することができ、さらに、インターフェース内でホットな関数に対してアセンブリコードを表示することができます。関数に元のソースコード情報がある場合、注釈の付いた出力にはアセンブリコードだけでなく、関数の実際のソースコードも表示されます。![blog_post4.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3486144/701a4621-d824-f681-9b57-e3461a75aa1f.png)

この機能は、Linux perfのプロファイリング出力でソースコードが関数にマッピングされるように、CheerpXで実行される元のマシンコードをブラウザで実行されるネイティブコードにマッピングするアイデアを生み出しました。これを実現するために、Linux perfのソースコードと私たち自身の技術の両方に対するより深い理解が必要でした。

このカスタムワークフローを実現する最初のステップは、CheerpXの新しい内部機能を実装することでした。前述のように、CheerpXは2段階の実行エンジンで構築されているため、WebAssemblyにコンパイルされる直前のネイティブのX86マシンコードを収集する方法が必要でした。このステップでは、マシンコードの状態を理解することが重要であり、結果として、WebAssemblyにコンパイルされる直前のすべてのネイティブコードブロックを印刷することを可能にするシンプルなプリプロセッサ定義が実装されました。

次のステップは、ネイティブコードブロックをプロファイリングレポートに追加することでした。このステップでは、ELF（Executable and Linkable Format）、DWARF（Debugging With Attributed Record Formats）、およびlinux perfのJITダンプ用のカスタムバイナリ形式などのファイル形式に精通する必要がありました。linux perfとV8を使用したプロファイリングプロセスでは、V8はjitdumpバイナリを生成し、レポートに含まれるすべてのジット関数に関する情報が格納されます。Linux perfはこれらのjitdumpファイルを処理し、jitdumpに含まれるすべてのジット関数のためにELFオブジェクトファイルを生成します。ELFオブジェクトファイルには、関数の開始アドレス、デバッグ情報、ソースコード、アセンブリコードなど、関数に関するすべての必要な情報が含まれています。これらのバイナリ形式を読み込んだ結果、ネイティブのソースコードをperfレポートに組み込む最良の方法は、jitdumpファイル自体を更新し、linux perfにELFオブジェクトファイルの処理を任せることであると結論しました。

これで、jitdumpバイナリファイルを処理し、更新するために必要なすべての重要な情報とデータを手に入れることができました。しかし、バイナリファイルを扱っている最中に、次の課題に直面しました：Linuxカーネルのソースコードに存在するバグです。

# Linuxカーネルのパッチ適用
linux perfのソースコードをより深く理解し、jitdumpの形式を理解するために、Linuxカーネルのソースビルドを用いて作業を開始しました。最初のテストランで、ジットされた関数がプロファイリングレポートにソースコード情報が表示されないことに気付きました。表示されるのは行番号だけで、追加のソースコード情報は何もありませんでした。![2.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3486144/437e2837-47e1-758b-554a-d7853fdb5299.png)
このプロジェクトのこの段階では、すでにDWARFデバッグファイル形式に精通していました。これは、ELFオブジェクトファイルに含まれるすべてのデバッグ情報を持っています。'objdump'のようなツールを使用して、ジットされた関数のDWARFデータに関する追加情報を取得できました。デバッグ情報がELFファイルに挿入されていることを確認しましたが、正しく挿入されていないことも確認しました。

Linuxカーネルのソースコードを修正した後、問題が確認されました。関数のファイル名がELFオブジェクトに正しく挿入されることを確認するための1つのチェックが間違っていました。必要なコードの調整を行い、変更を整理し、Linuxカーネルにパッチを提出しました。そのパッチは正常に受け入れられ、linux-nextツリーにマージされました。linux-nextツリーは次のカーネルマージウィンドウを目指すパッチの保管場所です。![1-e1687954580262.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3486144/61bdd04f-03bd-09dc-e8d1-48c5c33f8d8d.png)
# カスタムプロファイリングワークフロー
Linux perfが再び正常に動作するようになり、Leaning Technologies向けのカスタムプロファイリングワークフローを成功裏に完成させました。最終的な成果物は、Chromeを起動し、アクティブなタブをlinux perfを用いてプロファイリングするカスタムスクリプトです。さらに、CheerpXからネイティブ命令を収集し、それをjitdumpバイナリファイルに組み込みます。Leaning TechnologiesでWebVMをプロファイリングする際には、ネイティブコードブロックがperfプロファイリングレポートにシームレスに統合されるようになります。![3.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3486144/4b408b4f-cf66-c511-cae9-2a00207a3f95.png)

# 自分で試してみる
もしこの投稿がプロファイリングやLinux perf、Chromiumの探求心を刺激したならば、私が[GitHub repository](https://github.com/leaningtech/chrome-profiling)に便利なツールや情報をまとめて用意しています。Linux perfのソースからビルドする手順や、ツール自体の基本的な詳細、一般的なコマンドが記載されたLinux perfチートシート、そしてLinux perfとChromeのチートシートが一か所にまとめられています。

ツールに慣れてきたら、私たちのカスタムスクリプトの1つを活用できます。このスクリプトはChromiumを起動し、Linux perfを用いて開いているタブを記録するように設計されており、ユーザーフレンドリーな体験を提供します。

ぜひリポジトリを探索し、さらなるアップデートをお楽しみください。質問があれば、[Discord](https://discord.gg/yTNZgySKGa)で私たちに参加するか、[Twitter](https://twitter.com/panicodes)で私と直接コンタクトしてください。

[引用元](https://new.leaningtech.com/beyond-devtools-profiling-webapps-at-the-assembly-level/)

https://leaningtech.com/legacy-modernisation-jp/