カーネルレベルでの SquashFS の最適化(Android 9 以前)

SquashFS は Linux 用の読み取り専用圧縮ファイルシステムです。このファイル システムは読み取り専用に設計されているため、システム パーティションでの使用に適しています。多くの Android デバイスでは、システム パーティションにこのファイル システムを使用するメリットがあります。たとえば次のようなデバイスです。

  • Android Watch など、ストレージ容量の少ないデバイス。
  • 低速なフラッシュ ストレージを搭載したデバイス(圧縮によりブロック I/O が減ります)。

残念ながら、SquashFS のパフォーマンスは ext4 に劣ります。

最適化

SquashFS には、次のようなパフォーマンス改善手段が実装されています。

メモリ使用量と memcpy を減らす

ブロックを読み込むとき(デフォルト 128 K)、そのブロックを包含するすべてのページを確保しようとします。

1 つのページが最新である場合、あるいはロックされている場合は、ブロック全体を割り当て、読み取りリクエストを発行して、その内容をページにコピーします。

この方法はかなり非効率です。しばらくすれば、ページ キャッシュには、隣接ページが最新でない場合でも、最新のページが含まれるようになる可能性があります。

穴(欠けているページ)のあるブロックを扱えるようになりました。これにより、次のようにしてパフォーマンスが向上します。

  • memcpy の呼び出しを減らす
  • メモリ割り当てを減らす

非同期読み取り

SquashFS では、サポート終了となった ll_rw_block() 関数がまだ使われています。この方法には、次の 2 つの問題があります。

  • 名前が示すように、この関数は読み込みが完了するのを待ってから戻ります。これは、.readpage() がすでにページのロックでウェイトしているため冗長です。さらに、効率的な .readpages() を実装するために非同期機構が必要です。
  • 読み取りリクエストの結合は、完全に I/O スケジューラに依存しています。 ll_rw_block() は 1 つのバッファにつき 1 つのリクエストを作成するだけです。どれを結合すればよいかについて、SquashFS には I/O スケジューラよりも多くの情報があります。さらに、リクエストを結合すると I/O スケジューラへの依存度が減ります。

これらの理由から、ll_rw_block() 関数は submit_bio() に置き換えられました。

readpages(プリフェッチ)

SquashFS では .readpages() が実装されていないため、カーネルは .readpage() を繰り返し呼び出します。

読み取りリクエストが非同期になったので、カーネルは非同期先読み機構を使用して実際にページをプリフェッチできます。

非圧縮ブロックの読み込みを最適化する

Android などの最新のシステムには、圧縮済みのファイルが多数含まれています。その結果、イメージには圧縮不可能なブロックが多数含まれています。

SquashFS は、1 つのページを読み込むとき、圧縮ブロックと非圧縮ブロックは同じロジックを使用して扱い、実際にブロック全体を読み込みます(デフォルトで 128 k)。これは、圧縮ブロックには必要ですが、非圧縮ブロックの場合はリソースの無駄です。

SquashFS は、ブロック全体を読み取るのではなく、先読みアルゴリズムによって推奨されるものだけを読み込むようになりました。

これにより、ランダム読み取りのパフォーマンスが大幅に向上します。

コード

SquashFS のコード最適化は、AOSP で入手できます。