Длительное время компиляции для программы со статическим размещением - PullRequest
16 голосов
/ 12 февраля 2011

Буду очень признателен, если кто-нибудь скажет мне, почему компиляция этой программы:

double data[123456789];  
int main() {}

занимает в 10 раз больше времени, чем компиляция этого:

int main() {
    double* data=new double[123456789];
}

когда оба компилируются с:

$ g++ -O0

и исполняемые файлы имеют почти одинаковый размер.

Я использую gcc 4.4.3 в Ubuntu 10.04.

Спасибо.

1 Ответ

25 голосов
/ 13 февраля 2011

Динамическое распределение

Ваша вторая программа выделяет память во время выполнения;с точки зрения компилятора, нет никакой разницы между компиляцией любого из следующего:

double *data = new double[123456789];
double *data = malloc(123456789);
double data  = sqrt(123456789);

Все они делают разные вещи, но все, что нужно компилятору, это генерировать вызов внешней функции с фиксированнойаргумент.Это можно увидеть, если использовать g++ -S для генерации сборки:

.text
main:
    subq    $8, %rsp          /* Allocate stack space. */
    movl    $987654312, %edi  /* Load value "123456789 * 8" as argument. */
    call    _Znam             /* Call the allocation function. */
    xorl    %eax, %eax        /* Return 0. */
    addq    $8, %rsp          /* Deallocate stack space. */
    ret

Это просто для генерации любого компилятора и любого компоновщика для линковки.

Статическое распределение

Ваша первая программа, однако, немного хитрая, как вы заметили.Если мы посмотрим на сборку для нее, мы увидим, что происходит что-то другое:

.text
main:
    xorl    %eax, %eax        /* Return 0. */
    ret

.bss
data:
    .zero   987654312         /* Reserve "123456789 * 8" bytes of space. */

Сгенерированная сборка запрашивает 123456789 * sizeof(double) байт пространства, которые должны быть зарезервированы при первом запуске программы.Когда он собран и позднее связан (что происходит за кулисами, если вы просто запускаете g++ foo.c), компоновщик ld фактически выделит все это зарезервированное пространство в памяти.Это где время идет.Если вы запустите top во время работы g++, вы увидите, что ld высосет большой объем памяти вашей системы.

Сокращенные размеры исполняемых файлов

Резонный вопрос может быть: «Почему мой исполняемый файл не становится действительно большим, если память резервируется во время компоновки?».Ответ скрыт в маркере .bss в сборке.Это говорит компоновщику, что данные, определенные ниже, не должны храниться в конечном исполняемом файле, а вместо этого должны быть присвоены нулю во время выполнения.

Это оставляет нам следующую последовательность шагов:

  1. Ассемблер сообщает компоновщику, что ему необходимо создать раздел памяти длиной 1 ГБ.

  2. Компоновщик запускает и выделяет эту память, готовясь кпоместив его в окончательный исполняемый файл.

  3. Компоновщик понимает, что эта память находится в разделе .bss и помечена NOBITS, что означает, что данные равны 0, и нене должно быть физически помещено в конечный исполняемый файл.Он позволяет избежать записи 1 ГБ данных, вместо этого просто выбрасывая выделенную память.

  4. Компоновщик записывает в конечный файл ELF только скомпилированный код, создавая небольшой исполняемый файл.

Умный компоновщик может избежать шагов 2 и 3, описанных выше, что значительно ускоряет время компиляции.В действительности, такие сценарии, как ваш, на практике встречаются недостаточно часто, чтобы оправдать такую ​​оптимизацию.

Динамическое или статическое распределение

Если вы пытаетесьОпределите, что из вышеперечисленного (динамическое или статическое распределение) на самом деле использовать в вашей программе, вот несколько соображений:

  • Компоновщик должен будет использовать столько же памяти, сколько и ваша конечная программа(плюс немного).Если вы хотите статически выделить 4 ГБ ОЗУ, вам понадобится 4 ГБ ОЗУ для вашего компоновщика.Это не подразумевается в том, как работают линкеры, но, скорее, это просто способ их реализации.

  • При выделении больших объемов памяти динамическое выполнение позволяет вам работать лучшеобработка ошибок (отображение на экране удобного для пользователя сообщения, объясняющего, что у вас недостаточно памяти, вместо того, чтобы просто не загружать исполняемый файл с сообщением из ОС, идущим к пользователю);

  • Динамическое распределение памяти позволяет вам выбирать, сколько памяти выделять в зависимости от ваших реальных потребностей.(Если data является кешем, пользователь может выбрать размер кеша, если он хранит промежуточные результаты, вы можете изменить его размер в зависимости от проблемы и т. Д.)

  • Динамическое распределениепамять позволяет вам освободить ее позже, если ваша программа должна продолжить работу и выполнить больше работы после того, как она будет завершена с памятью.

В конце концов, если вышеуказанные пункты не имеют значения, и вы можете иметь дело с более длительным временем компиляции, это, вероятно, не имеет большого значения. Статическое распределение памяти может быть намного проще и часто является правильным подходом для небольших объемов памяти или одноразовых приложений.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...