コンパイルされたJavaクラスファイルがCコンパイルされたファイルよりも小さいのはなぜですか?

StackOverflow https://stackoverflow.com/questions/4838221

質問

「Hello、World!」を印刷する.cファイルをコンパイルすることで得られる.oファイルが得られる理由を知りたいと思います。 「Hello、World!」も印刷するJava .classファイルよりも大きいですか?

役に立ちましたか?

解決

JavaはBytecodeを使用してプラットフォームに独立して「プリコンパイル」されますが、Bytecodeはインタープリターで使用され、十分にコンパクトに提供されるため、コンパイルされたCプログラムで見ることができるマシンコードと同じではありません。 Javaコンピレーションの完全なプロセスをご覧ください。

Java program  
-> Bytecode   
  -> High-level Intermediate Representation (HIR)   
    -> Middle-level Intermediate Representation (MIR)   
      -> Low-level Intermediate Representation (LIR)  
        -> Register allocation
          -> EMIT (Machine Code)

これは、コード変換を機械するためのJavaプログラムのチェーンです。ご覧のとおり、ByteCodeはマシンコードから遠く離れています。インターネットでは、実際のプログラム(例)でこの道を見せるための良いものを見つけることができません。私が見つけたものはすべてです このプレゼンテーション, 、ここでは、各ステップがコードプレゼンテーションをどのように変更するかを確認できます。コンパイルされたCプログラムとJava Bytecodeが異なる方法と理由に答えることを願っています。

アップデート:「bytecode」の後にあるすべての手順は、そのコードをコンパイルする決定に応じて実行時にJVMによって行われます(これは別のストーリーです... JVMはバイトコード解釈とネイティブプラットフォームに依存するコードへのコンパイルの間でバランスを取ります)

最終的に良い例を見つけました Java Hotspot™クライアントコンパイラのリニアスキャンレジスタ割り当て (ところで、JVM内で何が起こっているのかを理解するための良い読書)。 Javaプログラムがあると想像してください:

public static void fibonacci() {
  int lo = 0;
  int hi = 1;
  while (hi < 10000) {
    hi = hi + lo;
    lo = hi - lo;
    print(lo);
  }
}

その後、そのbytecodeは次のとおりです。

0:  iconst_0
1:  istore_0 // lo = 0
2:  iconst_1
3:  istore_1 // hi = 1
4:  iload_1
5:  sipush 10000
8:  if_icmpge 26 // while (hi < 10000)
11: iload_1
12: iload_0
13: iadd
14: istore_1 // hi = hi + lo
15: iload_1
16: iload_0
17: isub
18: istore_0 // lo = hi - lo
19: iload_0
20: invokestatic #12 // print(lo)
23: goto 4 // end of while-loop
26: return

各コマンドは1バイトを取得します(JVMは256コマンドをサポートしますが、実際にはその数よりも少ない) +引数があります。一緒に27バイトが必要です。すべての段階を省略し、ここでマシンコードを実行する準備ができています。

00000000: mov dword ptr [esp-3000h], eax
00000007: push ebp
00000008: mov ebp, esp
0000000a: sub esp, 18h
0000000d: mov esi, 1h
00000012: mov edi, 0h
00000017: nop
00000018: cmp esi, 2710h
0000001e: jge 00000049
00000024: add esi, edi
00000026: mov ebx, esi
00000028: sub ebx, edi
0000002a: mov dword ptr [esp], ebx
0000002d: mov dword ptr [ebp-8h], ebx
00000030: mov dword ptr [ebp-4h], esi
00000033: call 00a50d40
00000038: mov esi, dword ptr [ebp-4h]
0000003b: mov edi, dword ptr [ebp-8h]
0000003e: test dword ptr [370000h], eax
00000044: jmp 00000018
00000049: mov esp, ebp
0000004b: pop ebp
0000004c: test dword ptr [370000h], eax
00000052: ret

結果に83(16進バイト + 1バイト)バイトが必要です。

詩リンク(他の人が言及した)とコンパイルされたファイルヘッダー(おそらく違うかもしれません。Cでどのようにあるかわかりませんが、すべての文字列が移動されます特別なヘッダープール、およびプログラムでは、ヘッダーなどでその「位置」を使用しています。

update2: X86および他のほとんどのプラットフォームに基づいたマシンコードはレジスタで動作しますが、JavaはStack(Istore/Iloadコマンド)で動作することを言及する価値があります。ご覧のとおり、マシンコードはレジスタの「フル」であり、より単純なスタックベースのバイトコードと比較して、コンパイルされたプログラムに余分なサイズを与えます。

他のヒント

この場合のサイズの違いの主な原因は、ファイル形式の違いです。エルフのこのような小さなプログラム形式の場合(.o)ファイルは、スペースの面で深刻なオーバーヘッドを導入します。

たとえば、私のサンプル .o 「Hello、World」プログラムのファイルが取られます 864バイト. 。それは(で探索された)で構成されています readelf 指図):

  • 52バイトのファイルヘッダー
  • 440バイトのセクションヘッダー(40バイトx 11セクション)
  • セクション名の81バイト
  • シンボルテーブルの160バイト
  • 43バイトのコード
  • 14バイトのデータ (Hello, world\n\0)

.class 同様のプログラムのファイルにのみ使用されます 415バイト, 、それにはより多くのシンボル名が含まれており、これらの名前は長いという事実にもかかわらず。それは(で探索された)で構成されています Java Class Viewer):

  • 定数プールの289バイト(定数、シンボル名などを含む)
  • メソッドテーブルの94バイト(コード)
  • 属性テーブルの8バイト(ソースファイル名の参照)
  • 24バイトの固定サイズヘッダー

参照:

Cプログラムは、プロセッサで実行されるネイティブマシンコードにコンパイルされていますが(もちろん、OSを介して発送されます)、オペレーティングシステムのために多くのセットアップと引き裂きを行う必要があり、動的にリンクされていますCライブラリなどのライブラリ

一方、Javaは、仮想プラットフォーム(基本的にシミュレートされたコンピューターのコンピューターのシミュレートされたコンピューター)のByteCodeにコンパイルされています。コードとVMインターフェイスは明確に定義されています)はVM自体に移動して、プログラムコードを無駄のないままにします。

ただし、コンパイラからコンパイラまでさまざまであり、コードを削減したり、コードを構築したりするオプションがいくつかあります。

これはすべて、それはそれほど重要ではありません。

要するに、JavaプログラムはJava Byteコードにコンパイルされます。これには、個別のインタープリター(Java仮想マシン)を実行する必要があります。

Cコンパイラによって生成された.oファイルが、Javaコンパイラが作成した.classファイルよりも小さくなるという100%の保証はありません。それはすべて、コンパイラの実装に依存します。

サイズの違いの主な理由の1つ .o.class ファイルは、Javaバイトコードがマシンの命令よりも少し高いレベルであるということです。もちろん、それほど高度なレベルではありません - それはまだかなり低レベルのものです - しかし、それは効果的に機能するので違いを生みます 全体 プログラム。 (CとJavaの両方のコードには、起動コードがあります。)

もう1つの違いは、Javaクラスファイルがしばしば比較的小さな機能を表していることです。さらに小さなピースにマッピングするCオブジェクトファイルを使用することは可能ですが、多くの場合、単一のファイルに(関連)機能をより多く(関連する)機能させる方が一般的です。スコーピングルールの違いは、これを強調するために作用する可能性があります(Cにはモジュールレベルのスコープに対応するものは実際にはありませんが、代わりにファイルレベルのスコープがあります; Javaのパッケージスコープは複数のクラスファイルで動作します)。プログラム全体のサイズを比較すると、より良いメトリックが得られます。

「リンクされた」サイズに関しては、Java実行可能ファイルファイルは、圧縮されているため、(特定のレベルの機能性の場合)小さくなる傾向があります。 Cプログラムを圧縮形式で提供することは比較的まれです。 (標準ライブラリのサイズにも違いがありますが、CプログラムはLIBC以外のライブラリが存在することを期待できるため、洗浄かもしれません。厄介です。)

次に、情報をデバッグするという問題もあります。特に、IOをデバッグしてCプログラムをコンパイルすると、IOを含む標準ライブラリのタイプに関する多くの情報が得られます。 Javaコードには、実際のコンパイルされたコードに関するデバッグ情報のみが含まれます。これは、オブジェクトファイルで利用可能な関連情報を期待できるためです。これにより、コードの実際のサイズが変更されますか?いいえ。ただし、ファイルサイズに大きな影響を与える可能性があります。

全体として、CとJavaプログラムのサイズを比較するのは難しいと思います。むしろ、それらを比較することができ、簡単に役立つことはできません。

エルフ形式のほとんど(単純な機能の場合は90%) .o ファイルはジャンクです。のために .o 単一の空の関数本文を含むファイルでは、次のようなサイズの内訳が期待できます。

  • 1%コード
  • 9%シンボルと再配置テーブル(リンクに不可欠)
  • 90%のヘッダーオーバーヘッド、コンパイラおよび/またはアセンブラーによって保存されている役に立たないバージョン/ベンダーノートなど。

コンパイルされたCコードの実際のサイズを確認したい場合は、 size 指図。

クラスファイルはJava Byteコードです。

C/C ++ライブラリとオペレーティングシステムライブラリは、C ++コンパイラが最終的に実行可能なバイナリを作成するために作成するオブジェクトコードにリンクされているため、おそらく小さくなります。

簡単に言えば、バイナリの作成にリンクする前に、JavaバイトコードとCコンパイラによって作成されたオブジェクトコードを比較するようなものです。違いは、JVMがJava BYTEコードを解釈してプログラムが行うことを意図していることを適切に実行するために、cはオペレーティングシステムがインタープリターとして機能するため、オペレーティングシステムからの情報を必要とするという事実です。

また、すべてのシンボル(関数など)では、オブジェクトファイルのいずれかで少なくとも1回は外部ライブラリから参照されます。複数のオブジェクトファイルで使用している場合、1回だけインポートされています。この「インポート」が発生する可能性のある2つの方法があります。静的リンクを使用すると、関数の実際のコードが実行可能ファイルにコピーされます。これによりファイルサイズが増加しますが、外部ライブラリ(.dll/.soファイル)が必要ではないという利点があります。動的リンクでこれは起こりませんが、その結果、プログラムでは追加のライブラリを実行する必要があります。

Javaでは、いわばすべてが動的に「リンク」されています。

Javaは、マシン独立言語にまとめられています。これは、コンパイルされた後、実行時にJava Virtual Machine(JVM)によって翻訳されることを意味します。 Cは機械命令に編集されるため、プログラムがターゲットマシンで実行されるすべてのバイナリです。

Javaはマシン独立言語にコンパイルされるため、特定のマシンの特定の詳細はJVMによって処理されます。 (つまり、マシン固有のオーバーヘッドがあります)

それが私がとにかくそれについて考える方法です:-)

いくつかの潜在的な理由:

  • Javaクラスファイルには、初期化コードがまったく含まれていません。それはあなたの1つのクラスとその中に1つの機能があるだけです - 確かに非常に小さいです。それに比べて、Cプログラムには、ある程度の静的にリンクされた初期化コードと、おそらくDLLサンクがあります。
  • Cプログラムには、ページの境界に並べられたセクションがある場合があります。これにより、コードセグメントがページ境界で開始されるように、そのようなプログラムサイズに最低4kBが追加されます。
ライセンス: CC-BY-SA帰属
所属していません StackOverflow
scroll top