Почему скомпилированные файлы классов Java меньше, чем скомпилированные файлы C? - PullRequest
12 голосов
/ 29 января 2011

Я хотел бы знать, почему файл .o, который мы получаем при компиляции файла .c, который печатает «Hello, World!» больше, чем файл Java .class, который также печатает «Hello, World!»?

Ответы [ 9 ]

13 голосов
/ 29 января 2011

Java использует байт-код, чтобы быть независимым от платформы и «предварительно скомпилированным», но байт-код используется интерпретатором и подается достаточно компактным, так что это не тот машинный код, который вы можете видеть в скомпилированной программе на Си. Просто взгляните на весь процесс компиляции 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-программы в машинный код. Как видите, байт-код находится далеко от машинного кода. Я не могу найти в Интернете хороших вещей, чтобы показать вам этот путь в реальной программе (пример), все, что я нашел, это эта презентация , здесь вы можете увидеть, как каждый шаг меняет представление кода. Я надеюсь, что это ответит вам, как и почему скомпилированная программа c и байт-код Java отличаются.

UPDATE: Все этапы после «байт-кода» выполняются JVM во время выполнения в зависимости от его решения скомпилировать этот код (это другая история ... JVM балансирует между интерпретацией байт-кода и его компиляцией в собственный код, зависящий от платформы)

Наконец нашел хороший пример, взятый из Распределение регистров линейного сканирования для клиентского компилятора HotSpot ™ (кстати, хорошее чтение, чтобы понять, что происходит внутри JVM). Представьте, что у нас есть Java-программа:

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

тогда его байт-код:

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 (52 в гексах + 1 байт) байта.

PS. Я не принимаю во внимание ссылки (упоминавшиеся другими), а также заголовки файлов compiledc и bytecode (возможно, они тоже разные; я не знаю, как с c, но в файле bytecode все строки перемещаются в специальный пул заголовков, и в программе используется его «позиция» в заголовке и т. д.)

UPDATE2: Вероятно, стоит упомянуть, что java работает со стеком (команды istore / iload), хотя машинный код, основанный на x86 и большинстве других платформ, работает с регистрами. Как вы можете видеть, машинный код «полон» регистров, и это дает дополнительный размер скомпилированной программе по сравнению с более простым стековым байт-кодом.

7 голосов
/ 29 января 2011

Основной причиной различий в размерах в этом случае является разница в форматах файлов.Для такой небольшой программы формат файла ELF (.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 байта заголовков фиксированного размера

См. Также:

3 голосов
/ 29 января 2011
Программы

C, даже если они скомпилированы с собственным машинным кодом, работающим на вашем процессоре (разумеется, отправляемом через ОС), обычно требуют много настроек и сносов для операционной системы, загружаядинамически связанные библиотеки, такие как библиотека C и т. д.

Java, с другой стороны, компилируется в байт-код для виртуальной платформы (в основном, имитируемого компьютера в компьютере), который специально разработан вместе с Javaсама по себе, поэтому большая часть этих накладных расходов (если бы это даже было необходимо, поскольку и код, и интерфейс виртуальной машины четко определены) может быть перенесена в саму виртуальную машину, оставляя программный код простым.

Тем не менее, это зависит от компилятора, и есть несколько способов уменьшить его или построить код по-разному, что будет иметь разные последствия.

Все это говорит о том, что это не так уж важно.

1 голос
/ 29 января 2011

Большая часть (до 90% для простых функций) файла ELF-формата .o является ненужной. Для файла .o, содержащего одно пустое тело функции, можно ожидать разбивку по размеру, например:

  • 1% код
  • 9% таблица символов и перемещений (необходима для связи)
  • 90% заголовка, бесполезные заметки о версии / поставщике, сохраняемые компилятором и / или ассемблером и т. Д.

Если вы хотите увидеть реальный размер скомпилированного кода C, используйте команду size.

1 голос
/ 29 января 2011

Одна из ключевых причин различий в размерах файлов .o и .class заключается в том, что байт-коды Java немного выше уровня, чем машинные инструкции.Конечно, не очень высокий уровень - это все же довольно низкоуровневый материал - но это будет иметь значение, потому что он эффективно действует для сжатия программы целом .(Как код C, так и код Java могут содержать код запуска.)

Другое отличие состоит в том, что файлы классов Java часто представляют относительно небольшие части функциональности.Несмотря на то, что можно иметь объектные файлы на языке C, которые отображаются на еще более мелкие фрагменты, часто чаще помещать больше (связанных) функциональных возможностей в один файл.Различия в правилах области видимости также могут подчеркнуть это (на самом деле C не имеет ничего, что соответствует области действия на уровне модуля, но вместо этого у него есть область действия на уровне файла; область действия пакета Java работает с несколькими файлами классов).Вы получите лучший показатель, если вы сравните размер всей программы.

С точки зрения «связанных» размеров исполняемые файлы JAR Java имеют тенденцию быть меньше (для данного уровня функциональности), потому что они поставляютсясжат.Относительно редко доставлять программы на С в сжатом виде.(Существуют также различия в размере стандартной библиотеки, но они также могут быть «промывкой», потому что программы на C могут рассчитывать на наличие библиотек, отличных от наличия libc, а программы на Java имеют доступ к огромной стандартной библиотеке.это неудобно.)

Тогда есть также вопрос отладки информации.В частности, если вы скомпилируете C-программу с отладкой, которая выполняет IO, вы получите много информации о типах в стандартной библиотеке, включенной только потому, что это слишком неудобно для ее фильтрации.Код Java будет иметь только отладочную информацию о фактическом скомпилированном коде, потому что он может рассчитывать на соответствующую информацию, доступную в объектном файле.Меняет ли это реальный размер кода?Нет. Но это может иметь большое влияние на размеры файлов.

В целом, я думаю, что трудно сравнивать размеры программ на C и Java.Или, скорее, вы можете сравнить их и легко узнать ничего полезного.

1 голос
/ 29 января 2011

Вкратце: Java-программы скомпилированы в байт-код Java, для которого требуется отдельный интерпретатор (виртуальная машина Java).

Нет 100% гарантии, что файл .o, созданный c-компилятором, меньше, чем файл .class, созданный компилятором Java.Все зависит от реализации компилятора.

0 голосов
/ 29 января 2011

Несколько возможных причин:

  • Файл класса Java вообще не содержит код инициализации.Он просто содержит один класс и одну функцию - очень маленький.Для сравнения, программа на C имеет некоторую степень статически связанного кода инициализации и, возможно, DLL.
  • Программа C также может иметь разделы, выровненные по границам страницы - это добавило бы минимум 4 КБ к размеру программыпросто так, чтобы сегмент кода начинался с границы страницы.
0 голосов
/ 29 января 2011

Java компилируется в машинно-независимый язык.Это означает, что после компиляции он транслируется во время выполнения виртуальной машиной Java (JVM).C скомпилирован с машинными инструкциями и поэтому является двоичным для программы, выполняемой на целевой машине.

Поскольку Java компилируется на машинно-независимый язык, конкретные детали для конкретной машины обрабатываютсяJVM.(т. е. у C накладные расходы, специфичные для машины)

Вот так я все равно думаю об этом: -)

0 голосов
/ 29 января 2011

Файл класса является байтовым кодом Java.

Скорее всего, он меньше, поскольку библиотеки C / C ++ и библиотеки операционной системы связаны с объектным кодом, который компилятор C ++ создает для окончательного создания исполняемого двоичного файла.

Проще говоря, это все равно, что сравнивать байт-код Java с объектным кодом, созданным компилятором C, прежде чем он будет связан для создания двоичного файла. Разница заключается в том, что JVM интерпретирует байт-код Java, чтобы правильно делать то, для чего предназначена программа, тогда как C требует информацию из операционной системы, поскольку операционная система выполняет функции интерпретатора.

Также в C Каждый символ (функции и т. Д.), На который вы ссылаетесь из внешней библиотеки, хотя бы один раз в одном из объектных файлов, импортируется. Если вы используете его в нескольких объектных файлах, он все равно будет импортирован только один раз. Есть два способа, которыми этот «импорт» может произойти. При статическом связывании фактический код функции копируется в исполняемый файл. Это увеличивает размер файла, но имеет то преимущество, что внешние библиотеки (файлы .dll / .so) не нужны. При динамическом компоновке этого не происходит, но в результате вашей программе требуются дополнительные библиотеки для запуска.

В Java все, так сказать, динамически «связано».

...