1. ホーム

Android 10(2)のART仮想マシンの詳細についてはこちら

2022-02-28 21:03:20

原点

前号"に引き続き。 Android 10のART Virtual Machine(I)を知る。 今年の春休みの10日間で、おそらくプロフマンとdex2oatを一通りやったと思います。驚いたことに、dex2oatは実際にすべてをもう一度見直すきっかけになりました。

Androidを深く理解するための4冊の本を書いてから、初めて挫折しました。dex2oatのバイトコードからマシンコードへのコンパイルは6章ですが、dex2oatのソースコード解析は9章にあるのですね。

私の混乱

NougatのARTの基礎があれば、AOSP 10のARTの作業も楽勝だろうと思っていましたが、意外にもまだ難しいようです。AOSP 10でのdex2oatの実行順序はすぐに整理できましたが(この時点ではAOSP 7.0とあまり変わりません)、途中から風景が大きく変わったように感じています。その中でも一番怖いのはこれです。dex2oatの機能の多くとそのコードに対応する機能の多くが、Java VMの技術そのものと何らかの密接な関係があることを強く感じるのですが、これらの機能と機能がJVMの問題を解決するために設計されているのかがよくわからないのです--友人の言葉を借りれば、"コードのすべての行は理解できるが、なぜ"はわからない--つまりは それが何であるかは知っているが しかし どうだろう なぜ

つまり、JVMをよく把握せず、Java言語自体の進化や技術全体の進化に伴ってJVM自体にも改善が必要なことを把握せずに、ARTのコードを解釈することに反応したのです。このコードの作者は、ART VMを改良する際に、あるルートを考えていたはずで、間違いなくパッと思いついたわけではありません。

私はこれまで上記の問題を、単に言語自体の理解不足に起因するものと考えていました。それが、以前の公開号でquickjsとjavascript言語を研究テーマにして、この分野を突破しようとしたことです。しかし、この道は失敗かもしれません。なぜなら、jsエンジン(実はjavascriptの仮想マシン)はjvmより難易度が低いとは言えないからです。また、quickjsエンジンは、jsのコードからquickjsのバイトコードへのコンパイルを伴いますが、jvmはすでにバイトコードを扱っているため、これは関与しません。

では、何が問題なのでしょうか?同時に、ARTを勉強することの利点に大きな疑問を持っています。こんなものを勉強して、本当に何が身についたのでしょうか?(この懐疑論は、個人的にはすでに非常に厳しいものでした)。仕方なく、他の人のJVMに関する本がどんなことを語っているのか、目を向けてみました。そこで、周さんの "Java仮想マシンの深層を読みました。JVM Advanced Features and Best Practices"の最新版をWeChat Book PCで2回読みました。思いがけない感動と理解をもたらしてくれました。

私が得た気づきの一部

まず、周さんの本は、私が思うに、深い理解が得られるものであることを申し上げたい。この"deep understanding"は、私の本のように、コードを出して一行ずつ説明していくようなやり方ではありません。非常に複雑なことを、最もシンプルで分かりやすい方法で表現しながら、その背景や成り立ち、今後の展開、さらには実際の値のケースまでが語られているのです。これはとてもとても難しいことで、quot;origin"の部分だけでも長期にわたるフォローアップ作業が必要なのです。シェノンのクラスは、以前に局を配置し、Javaのジェネリックの導入であり、一部の学生は、千の言葉をこぼしたが、Javaでジェネリックの導入に関する本と比較して、テキストの背後にある技術力のギャップは非常に明白である。

改めて、周さんの本から見えてきたことを紹介します。まずは、以下の点について。 テクノロジーを超えて学んだこと

次に、技術的な知見を少し。

まず、JVMは最初から純粋にJava言語のためのものではありませんでした。私は周さんの本を読むまで、このことに気づきませんでした。Java仮想マシンは、Javaプログラミング言語については何も知らず、クラスファイル形式という特定のバイナリ形式だけを知っている。クラスファイルには、Java Virtual Machineの命令(またはバイトコード)とシンボルテーブル、およびその他の付帯情報が含まれています。

つまり、JVMはJavaプログラミング言語とは何の関係もないのです。クラスファイルを認識するだけです。この発言によって、私のJVMに対する知識は完全に広がりました。私はずっとOSにこだわってきました。しかし、Linux Kernelは上下左右前後に他の人に弾き飛ばされてしまったので、デバイス側のJVMをターゲットに選びました。しかし、jvmをカーネル的な、qemu的な仮想マシンとして考えるのは初めてでした。また、今年は時間をかけて本物の仮想マシンQEMUを勉強しようと思っていました(ソースコードはダウンロードできます)。しかし、この文章は、それが鉄の靴を介してステップに判明、JVMはVM(実際にはまだJava言語との関係の様々な持っていますが、私は今、別の観点からそれを見て)、Java言語の死者にそれを結びつけることはありません。実際には、数十年にわたって、Java言語は、今日1から13に進化しているが、バイトコードは、1つだけ新しい呼び出し-ダイナミックを追加している . このような観点から、kotlinはJavaとは別の言語と考えることができます。適切なツールを使えば、C/C++/Javascriptのコードをクラスファイルにコンパイルして、代わりにjvmで実行させることも可能です。もう少し考えてみると、ある会社のコンパイラが、何十年も前からあるjvmと同じビジョンを持っているように見えるのです。

次に、なぜjvmの仕様にリンクの概念があるのか、今まで理解できなかった。私は若い頃、C++の開発に明け暮れていました。C++の開発では、リンクは多くの場合、コンパイル段階のものです。そして、より明示的な段階を経て行われます。つまり、まずコンパイルし、次にリンクし、最終的なバイナリを生成する。しかし、javaのプログラムを書いていると、リンクすること自体が感じられなくなる。なぜ、JVMにそれがあるのでしょうか?実は、リンキングを高次元で見れば理解できるのです。リンクは、(テストもせず、本から推察しただけですが)いろいろな言語に存在するはずです。なぜなら、自分が書いたコードから他人が書いたコードをどうやって呼び出すか、という基本的な問題を解決するために設計されているからです。C++の開発では、これはプログラマが能動的に行うことである。しかし、jvmの場合、リンクはjvmが行う。目的は同じで、自分のコードで呼び出されたシンボルを実際の場所に対応させることです。

それから、同時並行性の再認識です。javaには2010年頃に初めて出会った同時並行性がありますが、C++の開発者だった私は、スレッドの方が馴染みがあり、OSのスレッドスケジューリングは千倍優れていて、同時並行性は結局スレッドを使うことに変換され、自前のスケジューラを手に入れるのは本当に?と思っていました。しかし、近年go言語が使われるようになり、同時並行性の素晴らしさが再認識されるようになった。周さんの本には、並行処理を使った後のスループットを比較したテストが紹介されています。つまり、同時並行性には間違いなく価値があるのです。java言語も並行処理のネイティブサポートに追随するようですが、スケジューラの入手の難しさは本当に小さくないようです。どうなることやら。

最後に、周さんの本には、JITとAOTのメリット・デメリットがとても詳しく書かれています。これによって、ある部門のあるコンパイラがどうなっているのか、別の視点から見ることができるようになりました。篤志家はここでは多くを語らない。全体的な感想としては、JVMの開発はJava言語の開発と密接に関係しており、androidという小さな分野だけでjvmの開発を主導しようとすると、明らかに大きな、そして致命的な欠陥があると思います。

以上が、周さんの本を読んでの私の認識です。最近読んだ「Born to be Great」という本の中の言葉と合わせてみてください。人の成長には、おそらく2つの方法がある。 1つは、現実の認識の変化から来るものである と、もう一つは 私たち自身の行動を改善することです。 . 明らかに、私は今、JVMについて新しい認識を持ち、より自信を持つことができました。私は何冊かの本を集めましたが、確率的にはさらに進んで、ハイレベルな観点からJVMをよく理解できるようになるでしょう。

次に、profmanとdex2oatについて、私が発見したことを簡単に説明します。

デクス2オート

dex2oatはaosp10でよりきれいになり、いくつかのクラスはより論理的に配置されています。

nougatと比較すると、コンパクトdex、vdexなどが新たに追加されています。oatのファイル形式も少し変わりました。

まずはdex2oatの使い方から。コンパイルログファイルから、ホストでのboot.oatの生成に関する実行コマンドを引き出すのに苦労しました(aosp10では、makefileのPREOPTを外すと、実はエミュレータは能動的にoatファイルを生成せず、直接インタプリタで実行していることに注意。以前はnougatでこの問題はなかったのですが。(エラーの原因は、/data/dalvikvmを操作する権限がない、ということのようです.........)

テキスト版のコマンドと入力パラメーターは次のとおりです。

out/soong/host/linux-x86/bin/dex2oatd

--保存の取り消しを回避する

--write-invocation-to=out/soong/generic_x86_64/dex_apexjars/system/framework/x86_64/apex.invocation

--runtime-arg

-Xms64m

--runtime-arg

-Xmx64m

--コンパイラーフィルタ=スピードプロファイル

-profile-file=out/soong/generic_x86_64/dex_bootjars/boot.prof。

-dirty-image-objects=フレームワークス/ベース/コンフィグ/dirty-image-objects

--dex-file=out/soong/generic_x86_64/dex_apexjars_input/core-oj.jar

--dex-file=out/soong/generic_x86_64/dex_apexjars_input/core-libart.jar

--dex-file=out/soong/generic_x86_64/dex_apexjars_input/okhttp.jar

--dex-file=out/soong/generic_x86_64/dex_apexjars_input/bouncycastle.jar

--dex-file=out/soong/generic_x86_64/dex_apexjars_input/apache-xml.jar

--dex-file=out/soong/generic_x86_64/dex_apexjars_input/framework.jar

--dex-file=out/soong/generic_x86_64/dex_apexjars_input/ext.jar

--dex-file=out/soong/generic_x86_64/dex_apexjars_input/telephony-common.jar

--dex-file=out/soong/generic_x86_64/dex_apexjars_input/voip-common.jar

--dex-file=out/soong/generic_x86_64/dex_apexjars_input/ims-common.jar

--dex-file=out/soong/generic_x86_64/dex_apexjars_input/android.test.base.jar

--dex-location=/apex/com.android.runtime/javalib/core-oj.jar

--dex-location=/apex/com.android.runtime/javalib/core-libart.jar

--dex-location=/apex/com.android.runtime/javalib/okhttp.jar

--dex-location=/apex/com.android.runtime/javalib/bouncycastle.jar

--dex-location=/apex/com.android.runtime/javalib/apache-xml.jar

--dex-location=/system/framework/framework.jar

--dex-location=/system/framework/ext.jar

--dex-location=/system/framework/telephony-common.jar

--dex-location=/system/framework/voip-common.jar

--dex-location=/system/framework/ims-common.jar

--dex-location=/system/framework/android.test.base.jar

--generate-debug-info

--generate-build-id

--oat-symbols=out/soong/generic_x86_64/dex_apexjars_unstripped/system/framework/x86_64/apex.oat

--ストリップ

--oat-file=out/soong/generic_x86_64/dex_apexjars/system/framework/x86_64/apex.oat

--oat-location=out/soong/generic_x86_64/dex_apexjars/system/framework/apex.oat

--image=out/soong/generic_x86_64/dex_apexjars/system/framework/x86_64/apex.art

--ベース=0x70000000

--instruction-set=x86_64

--instruction-set-variant=x86_64

--instruction-set-features=default(インストラクションセット フィーチャー)。

--アンドロイドルート=out/empty

--no-inline-from=core-oj.jar

--abort-on-hard-verifier-error (ハードベリファイアエラー)

--generate-mini-debug-info (ミニデバッグインフォの生成)

上記のコマンドで、dex2oatにロギングを追加したり、他のCPUアーキテクチャをサポートするなどのマジックをして、ホスト上で実行し、dex2oatの実行フローを学ぶことができますが、あまり良いとは思えません・・・・。

vdexのファイルフォーマットであるoatはdex2oatに関与しています。時間の都合上、先に内容を簡単に説明します。こちらがoatのファイル形式です。nougatのoatと比較すると、dexファイルの中身が削除されているようです。

(以前のoatファイルはdexの情報で完結していました。)あとは、最初はあまり違和感がありません。

では、dexファイルはどこにあるのでしょうか?答えは、vdexの中にあります。以下はvdexファイルのフォーマットです。これは、ソースコードのコメントから抜粋したものです。vdexには、apk/jarから抽出されたdexファイルが格納されていると書かれています。

vdexファイルにはVerifierDepsがありますが、これはVMによるクラスファイルのチェックサム処理に関連しているようです。これはJVMのかなり重要な機能なので、後日、別途お話する機会があればと思います。ここも以前苦しんだところです。つまり、Verifierが何をしようとしているのか、さっぱりわからなかったのです...。まあ、今はたぶん行き先はわかっているんですけどね。

でvdexファイルが生成されるコードです。

また、aosp10では、dexファイルには、標準dexファイルとコンパクトdexファイルの2種類が存在します。まだ詳細を見る機会がないのですが。コンパクトdexでどの程度の圧縮が可能なのか、よくわかりません。この作品はART自体の最適化であるはずです。

最後に、dex2oatは、マシンコード生成時に、グラフカラーリングとリニアアロケーションをサポートするレジスタアロケーションアルゴリズムを使用しています。以下は、対応するコードのスクリーンショットです。

dex2oatについては、今のところ以上です。肝心なのは、一番最初にdex2oatを実行するコマンドです。このコマンドを使うことで、ホスト上でのdex2oatの実行の流れを追うことができます。

プロフマン

profmanは、PGO(profile guided optimzation)の基礎となるプロファイルファイルの生成に関連するものです。簡単に言うと、プロファイルはプログラム中のホットファンクションをカウントし、マシンコード生成はそのホットファンクションに対して異なる最適化を行うことができるのです。周さんの本で紹介されているPGOは多くの情報を含むことができますが、ARTのプロファイル情報はあまり見かけないように思います。

プロファイルファイルは、2つの方法で生成されます。

  • 人間が読めるテキストファイルから、profman によって .profile 形式に変換されます。これはアートブートイメージや一部のシステムコアサービスの jar パッケージに有用です。なぜなら、これによって、システムをやる前にバイトコードをマシンコードに変換する、いわゆるAhead of Timeコンパイルができるからです(なお、周さんの本には、JITとAOTのメリット・デメリットが非常に詳しく紹介されていますので、私の本の6章を読んだ後に読まないと理解できないでしょう)。

  • APPが実行されると、JITモジュールによって.profileファイルが生成されます。一定時間待った後、dex2oatはこのプロファイルファイルを使ってPGOを実装します。

最初の例を見てみましょう。framework/base/services/art-profileが典型的な例です。

この場合、人間が読めるプロファイル情報(テキストファイル)が表示されます。その内容の中心は、どのクラスに対応するどのメソッド(->の前)がホットな関数であるかということです。なお、各行の前にはHSPのような文字がありますが、これはHot、Start、Poststartupの略で、3つとも表しています。

Hotはホット関数、StartupとPoststartupはVM起動前/起動後のシナリオに対応する。この3つは理論上、異なる最適化手法を導くことができます。でも、今はまだ研究が深くないので、詳しくは言えないんですけどね。

ちなみに、システムクラスに対応するプロファイルファイルは、frameworks/base/config/boot-image-profile.txtにあります。これは最終的にbuild/soong/java/dexpreopt_bootjars.goを介してシステムクラスのPGOを指示する。

profman は人間が読めるファイルをもとに .profile ファイルを生成します after. .profile ファイルはプログラムにとって読みやすくなっています。そのファイルフォーマットは以下の通りです。

ここではこれ以上は言いません。後で機会があれば、もっと詳しく説明します。

最後に、プログラム実行時にプロファイルファイルが生成される場所について説明します。答えは、ART JITモジュールの中にあります。そのコードは以下の通りです。

jit は ProfileSaver スレッドを起動し、最終的なプロファイルファイルはこのスレッドによって処理されるように動作します。

最後に、アプリ開発でよく遭遇するアプリのパフォーマンス監視についてです。アプリのパフォーマンス監視は、上記のProfileとは別物です。アプリのパフォーマンス監視は、関数呼び出しに費やされる時間に着目しています。以下のコードのようにサンプリングされます。

上記のコードのelseブランチでは、アプリのパフォーマンス監視はメソッドに依存し、メソッドEntered/Exited/Unwindにいくつかのタイムスタンプを追加し、各関数呼び出しに費やされた時間を取得できるようにします。

ifブランチでは、各スレッドのコールスタックを定期的にスキャンするためにsampling_thread_関数を起動することもできます。スタックの先頭の変化を追跡することで、関数呼び出しについて知ることができる。要するに、関数呼び出しに費やされた時間を検出することが目的です。

フォローアップの手配

JVMと密接に関係する知識の蓄積に力を注ぎたい。ARTのソースコードを土台にすれば、この道はうまくいくと思います。JVMの把握は必須ですし、国レベルでは基礎となるコア技術にもっと投資することになると思うので、JVMは非常に適した突破口だと感じています。

そして最後に

  • 私が期待する結果は、私の本や記事、ブログから友人が学び、実行したことではなく、"シェノン、私はあなたの肩を踏んでいますよ!と言ってくれることです。

  • 学習面の話はもういい。この後、この公開ページでは、基本的なテクニックや新しいテクニックを学び、共有していく予定です。皆さんからの投稿も歓迎します。しかし、公開番号の「連絡先」のところでも言いましたが、鄭元潔の童話「親知らず」の一節に、「私には沈黙する権利があるが、あなたの言う一言一言が私のインスピレーションの源になる」という趣旨があり、私は感銘を受けました。私には黙秘権があるが、あなたの言う一言一言が私のインスピレーションの源になることがある"。ということで、影響力は一方通行ではなく、あなたから学ぶことも多そうです。

神農とその仲間たちによる雑文集

長押しでQRコードを認識し、フォローすることができます