Почему скомпилированные файлы классов Java меньше, чем скомпилированные файлы C?

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

Вопрос

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

Это было полезно?

Решение

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

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

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 байтах).

Пса Я не принимаю во внимание связь (упоминалось другие), а также за заголовки файлов скомпилированным и байт -кодом (вероятно, они тоже разные; я не знаю, как это с C, но в файле Bytecode все строки перемещаются в Специальный пул заголовка, а в программе используется его «положение» в заголовке и т. Д.)

Обновление2: Вероятно, стоит упомянуть, что Java работает со Stack (команды ISTORE/ILOAD), хотя машинный код, основанный на X86 и большинстве других платформ, работает с регистрами. Как вы можете видеть, машинный код является «полным» регистров, и это дает дополнительный размер скомпилированной программе по сравнению с более простым байт-кодом на основе стека.

Другие советы

Основной причиной различий в размерах в этом случае является разница в форматах файлов. Для такого небольшого формата программы эльфа (.o) Файл вводит серьезные накладные расходы с точки зрения пространства.

Например, мой образец .o Файл программы «Привет, мир» принимает 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 Программы, хотя они и составлены в нативный машинный код, который работает на вашем процессоре (конечно, отправляется через ОС), как правило, нужно сделать много настроек и разрыва для операционной системы, загружая динамически связанную Библиотеки, такие как библиотека C и т. Д.

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

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

Все это сказано, это не совсем так важно.

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

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

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

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

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

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

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

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

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

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

Файл класса - код байта Java.

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

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

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

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

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

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

Во всяком случае, именно так я думаю об этом :-)

Несколько потенциальных причин:

  • Файл класса Java вообще не включает код инициализации. В нем просто есть ваш один класс и одна функция - действительно очень маленькая. Для сравнения, программа C имеет некоторую степень статически связанного кода инициализации и, возможно, DLL Thunks.
  • Программа C также может иметь разделы, выровненные по границам страниц - это добавило бы минимум 4 КБ к размеру программы, точно так же, чтобы гарантировать, что сегмент кода запускается на границе страницы.
Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top