共有ライブラリを使用すると、Linux でのアプリの起動が遅くなるのはなぜですか?
-
05-07-2019 - |
質問
私が取り組んでいる組み込みデバイスでは、起動時間は重要な問題です。アプリケーション全体は、一連のライブラリを使用するいくつかの実行可能ファイルで構成されます。FLASH メモリのスペースは限られているため、共有ライブラリを使用したいと考えています。
アプリケーションは、コンパイルして共有ライブラリとリンクすると通常どおりに動作し、FLASH メモリの量は予想どおり削減されます。静的ライブラリにリンクされているバージョンとの違いは、アプリケーションの起動時間が約 20 秒長いことですが、その理由はわかりません。
アプリケーションは、Linux 2.6.17 OS、16 MB Flash(JFFSファイルシステム)、32 MB RAMを使用して、180 MHzのARM9 CPUで実行されます。
解決
共有ライブラリは、通常はdlopen()または同様の方法で実行時にリンクする必要があります。静的ライブラリにはこのような手順はありません。
編集:詳細。 dlopenは次のタスクを実行する必要があります。
- 共有ライブラリを見つける
- メモリに読み込む
- すべての依存関係(およびその依存関係...)を再帰的にロードします
- すべてのシンボルを解決
これを実行するには、非常に多くのIO操作が必要です。
静的にリンクされたプログラムでは、上記のすべては実行時ではなくコンパイル時に行われます。したがって、静的にリンクされたプログラムをロードする方がはるかに高速です。
あなたの場合、違いは、コードを実行する必要がある比較的遅いハードウェアによって誇張されています。
他のヒント
これは、速度とスペースの古典的なトレードオフの良い例です。
すべての実行可能ファイルを静的にリンクして、実行速度を上げることができますが、その場合はより多くのスペースが必要になります
または
必要なスペースが少なく、ロードに時間がかかる共有ライブラリを使用できます。
だから、何を犠牲にするかを決める。
この違いには多くの要因(OS、コンパイラーなど)がありますが、理由の適切なリストはこちら。基本的に共有ライブラリは、スペース上の理由と「マジック」の多くのために作成されました。それらを機能させるためには、パフォーマンスが低下します。
(歴史的な注意事項として、Linux / Unixの元のNetscapeナビゲーターは、静的にリンクされた大きなファット実行可能ファイルでした。)
これは、同様の問題を持つ他の人を助けるかもしれません:
私の場合、起動に時間がかかった理由は、GCCのデフォルト設定はライブラリ内のすべてのシンボルをエクスポートすることでした。 大きな改善点は、コンパイラ設定「-fvisibility = hidden」を設定することです。
ライブラリがエクスポートする必要があるすべてのシンボルは、ステートメントで拡張する必要があります
__ attribute__((visibility(" default")))
gcc wiki
を参照
および非常にすばらしい記事共有ライブラリの作成方法
さて、共有ライブラリの使用には速度に関する欠点があることがわかりました。 動的リンクと読み込みエンライトに関するこの記事を見つけました。読み込みプロセスは、予想よりはるかに長いようです。
興味深い..通常、共有ライブラリの読み込み時間は、静的にリンクされている太ったアプリからは気づかれません。だから、システムがフラッシュメモリからライブラリをロードするのが非常に遅いか、ロードされたライブラリが何らかの方法でチェックされている(たとえば、.NETアプリはロードされたすべてのdllのチェックサムを実行し、ある場合)。共有ライブラリが必要に応じてロードされ、その後アンロードされて、構成の問題を示している可能性があります。
したがって、申し訳ありませんが理由を言うことはできませんが、ARMデバイス/ OSの問題だと思います。スタートアップコードをインスツルメントしたり、最も一般的に使用されるライブラリの1つと静的にリンクして、大きな違いが生じるかどうかを確認したことがありますか。また、共有ライブラリをアプリと同じディレクトリに配置して、FSでライブラリを検索するのにかかる時間を短縮します。
私にとって明らかなオプションの 1 つは、複数のプログラムをすべて 1 つのバイナリに静的にリンクすることです。そうすることで、可能な限り多くのコード (おそらく以前より多く) を共有し続けることができますが、動的リンカーのオーバーヘッドも回避でき、システム上に動的リンカーを配置するスペースも節約できます。
複数の実行可能ファイルを 1 つに結合するのは非常に簡単です。通常は argv を調べ、それに基づいてどのルーチンを呼び出すかを決定するだけです。