LINUX.ORG.RU

При компиляции однобайтового массива на 100Мб gcc cъедает 15GB RAM

 , , ,


2

3

Есть сишный файл enclose_io_memfs.c с одним единственным массивом. Весь массив забит разными значениями, не нулями.

#include <stdint.h>
#include <stddef.h>

const uint8_t enclose_io_memfs[114798592] = { 104
,115,113,115,104,55,0,0,25,234,210,93,0,0,2,0,239,1,0,0,1,0,17,0,192,0,1,0,4,0,0,0,48,8,227,225,1,0,0,0,27,167,215,6,0,0,0,0,19,167,215,6,0,0,0,0,255,255,255,255,255,255,255,255,107,96,211,6,0,0,0,0,71,69,213,6,0,0,0,0,200,84,215,6,0,0,0,0,157,166,215,6,0,0,0,0,120,218,140,189,7,92
,206,223,251,63,126,119,183,247,78,37,148,138,38,161,97,183,148,22,146,189,147,172,148,202,222,90,146,221,64,70,40,35,73,40,100,100,84,36,69,50,43,217,178,178,202,200,202,250,31,159,243,60,239,175,115,255,188,30,143,191,199,227,233,188,238,171,211,185,207,235,58,215,185,214,25,45,245,240,243,20,75,73,137,216,63,105,81,111,209,159,79,26,230,226,255,125,118,1,253,171,81
...
};

При самой обычной компиляции gcc -c enclose_io_memfs.c gcc 7.4 под убунтой 18.04 (стандартный, x64) съедает 15GB памяти! (1 процесс сс1). Размер *.с файла - 400Мб, размер получившегося *.о файла - где-то чуть больше 100Мб. Но почему он ест 15Gb и как его отучить от этого? --param ggc-min-expand=0 --param ggc-min-heapsize=0 не помогают.

Может он там что-то пытается оптимизировать? Если отключить оптимизацию:

#pragma GCC push_options
#pragma GCC optimize ("O0")

const uint8_t enclose_io_memfs[114798592] = { };

#pragma GCC pop_options

Будет ли побыстрее?

EXL ★★★★★
()

Конвертировать бинарь в С исходник не нужно:

asm(R"(.global my,myEnd;my:.incbin "test.cc";myEnd:)");
extern const char my[],myEnd[];
int main(){
...
}
anonymous
()
Ответ на: комментарий от EXL

Может он у него неограничен. В некоторых системах по умолчанию бывают сняты, например, ограничения на процессы.

К тому же переполнение он скорее схватит при попытке исполнить, а не скомпилировать, емнип.

grem ★★★★★
()
Последнее исправление: grem (всего исправлений: 1)
Ответ на: комментарий от grem

У него массив не на стеке, а в глобальной области видимости. Попадает куда-то в .rodata секцию ELF-файла.

EXL ★★★★★
()

Как измерял потребленную память? Скорее всего ты что-то попутал.

cvv ★★★★★
()

Ты представляешь себе как работает компилятор? Особенно оптимизирующий? Нехрен данные таким макаром в него совать.

lovesan ★★
()

Какое-то прищемление собственных яиц тисками. Зачем ты вообще binary data, тем более такого объёма, в сишные сырцы суёшь? Скорми бинарник линкеру чтоб он его тупо в какую-ниубдь одтдельную секцию бинарника записал, да и всё. Или хотя бы как тут выше предложили трюк с asm используй. Я уж молчу про то, что по-уму этот бинарник лучше отдельным файлом хранить и загружать/мапить по ходу работы когда и если потребуется.

Stanson ★★★★★
()

А не лучше ли было эти данные в бинарь какой запихнуть и оттуда уже читать? Так-то будет нормальный исполняемый файл и файл с данными, а не монстр с .rodata > код программы

Unicode4all ★★★★★
()

интересный случай, бывает еще, что htop->VIRT растет до 8-10GB и выше, в некоторых случаях это может быть индикатором утечек памяти

anonymous
()

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

abs ★★★
()

Всем спасибо. Во-первых, массив глобальный, поэтому память выделяется не в стеке. Во-вторых, эту хрень генерит ruby-packer, поэтому вторая категория ответов в стиле «нахрена ты это делаешь» и «как запихивать данные в бинарь» - тоже пролетает мимо. В третьих, мерял топом в параллельном окне - один процесс сжирает 15.2Gb виртуальной памяти, 7.3 физической, остальное из свап файла, имя процесса - cc1. Всё это действие длится минут 10, не меньше. А теперь ещё раз, вопрос по-существу, почему gcc компилятору для компиляции сишного файла с 100Мб инициализированным рандомными данными однобайтовым массивом требуется 15Гб памяти!!!?

repu1sion
() автор топика
Ответ на: комментарий от repu1sion

По-существу ответили выше ссылкой на баг.

Вообще 15 GiB на 100 MiB это ~150 байт на элемент инициализации. Компилятор хранит по узлу дерева на каждый элемент инициализации. Структура в 150 байт это не так уж и много учитывая, сколько разной информации там может быть для нужд компиляции в общем случае. Т.е. проблема в отсутствии оптимизации для представления константных инициализаторов.

xaizek ★★★★★
()
Ответ на: комментарий от repu1sion

А теперь ещё раз, вопрос по-существу, почему gcc компилятору для компиляции сишного файла с 100Мб инициализированным рандомными данными однобайтовым массивом требуется 15Гб памяти!!!?

Потому что ему надо хранить кучу данных чтобы делать разнообразные оптимизации.

Вот пример исходника:

#include <stdint.h>
#include <stdio.h>

const uint8_t c[] = {
	0x90, 0x81, 0x72, 0x63, 0x54, 0x45, 0x36, 0x27, 0x18, 0x09,
};

int main()
{
	printf("%d\n", c[6]);
	return 0;
}

gcc даже с -O0 компилирует это вот так:

(fcn) main 36
  int main (int argc, char **argv, char **envp);
; DATA XREF from entry0 @ 0x401061
0x00401126      55                     push    rbp
0x00401127      4889e5                 mov     rbp, rsp
0x0040112a      b836000000             mov     eax, 0x36 ; '6' ; 54
0x0040112f      0fb6c0                 movzx   eax, al
0x00401132      89c6                   mov     esi, eax
0x00401134      bf1a204000             mov     edi, 0x40201a ; const char *format ; "%d\n"
0x00401139      b800000000             mov     eax, 0
0x0040113e      e8edfeffff             call    sym.imp.printf ; int printf(const char *format)
; int printf("%d\n")
0x00401143      b800000000             mov     eax, 0
0x00401148      5d                     pop     rbp
0x00401149      c3                     ret

То есть заменяет вычисление смещения и чтения байта по адресу сразу на константу: mov eax, 0x36. Чтобы иметь возможность сделать так, компилятор должен в памяти хранить все значения массива, плюс кучу метаданных для каждого элемента массива. Причём постоянно, так как в момент обработки массива компилятор ещё не знает где именно и как он будет использоваться.

Во-вторых, эту хрень генерит ruby-packer

Пиши им багрепорт.

im-0
()

в этом файле только объявление массива и всё - он нигде не используется. даже с -O0 ему нужно ровно столько же - 15 Gb, о какой оптимизации речь? Комментатор выше прав - https://gcc.gnu.org/bugzilla/show_bug.cgi?id=12245 Этот баг тянется с 2003 года - 16 лет, я просто не поверил что такое возможно.

repu1sion
() автор топика
Ответ на: комментарий от repu1sion

Вот это поворот. Я думал что знаю все баги GCC на изусть.

cvv ★★★★★
()

Что будем делать? Будем завидовать, что есть возможность отдать gcc 16гб памяти )

One ★★★★★
()
Ответ на: комментарий от repu1sion

в этом файле только объявление массива и всё - он нигде не используется. даже с -O0 ему нужно ровно столько же - 15 Gb, о какой оптимизации речь?

Прочитай внимательно ещё раз то, что я написал. На момент обработки массива компилятор ещё не знает где и как он используется.

А баг - это скорее не баг, а фичреквест. Да, этот конкретный юзкейс можно как-то оптимизировать, но пока ни у кого руки не дошли.

Попробуй, кстати, clang. Вдруг там есть костыли для твоего случая.

im-0
()
Ответ на: комментарий от lovesan

А ты представляешь?

Просвети ка толпу по этому случаю, пожалуйста.

anonymous
()
Ответ на: комментарий от im-0

Всего то в полтора раза выше потребление памяти. Подумаешь, лишние 5Гб. Нахрен ты ползаешь в свой ассемблер если тебе на 5Гб рама фиолетово?

repu1sion
() автор топика
Ответ на: комментарий от repu1sion

ruby-packer

У этого проекта уже открыты ишью на тему выжирания десятков гигов

https://github.com/pmq20/ruby-packer/issues/74

Правильнее не тратить время на последствия забивания гвоздей микроскопом, при помощи генерации гигантских текстовых массивов, а помочь автору проекта довести его до ума.

В инете полно способов делать это оптимальнее: https://csl.name/post/embedding-binary-data/

anonymous
()
Ответ на: комментарий от anonymous

да, я видел эту багу. Если любишь руби и у тебя есть свободных пару недель - ковыряйся пожалуйста, это правильнее.

repu1sion
() автор топика
Ответ на: комментарий от repu1sion

Всего то в полтора раза выше потребление памяти. Подумаешь, лишние 5Гб. Нахрен ты ползаешь в свой ассемблер если тебе на 5Гб рама фиолетово?

То есть ты считаешь, что 10Gb - это нормально, а 15Gb - нет? Разница тут только в том, что llvm/clang на каждое значение хранит структуру чуть меньшего размера, чем gcc. Принципиальных отличий и специальных оптимизаций для этого случая в clang всё ещё нет.

im-0
()
Ответ на: комментарий от repu1sion

Я не пользуюсь этим приложением, меня не беспокоят жор памяти и десятки минут сборки.

У тебя два пути: ковыряться в рубевом проекте из 5 или 6 рубевых файлов (не знаю, что там делать две недели) или оптимизировать сишный gcc.

Мне кажется, по трудоемкости, если ты не один из действующих разработчиков gcc, выбор должен быть очевиден.

Ну можно поныть на лоре, но тем открытым ишью уже больше года и простых решений там не приведено.

anonymous
()

вообще я, кажется, начинаю понимать, почему clang имеет право на жизнь, и почему gcc лихорадочно апают номера версий, имитируя бурную деятельность, после 10 лет сидения на 4-ой.

repu1sion
() автор топика

Скажи мне - кто круче - Метапрог или петушиный Царь? Думаю Метапрог. У него есть верный соратник Котечка, он гей. Вдвоем с Метапрогом они оттарабанят Царя в сраку. Царь в это время только ругаться сможет. Даже корону не поправит.

anonymous
()
Вы не можете добавлять комментарии в эту тему. Тема перемещена в архив.