Malloc против обычного распределителя: у Malloc много накладных расходов. Зачем?

У меня есть приложение сжатия изображений, которое теперь имеет две разные версии систем распределения памяти. В исходном файле malloc используется везде, а во втором я реализовал простой пул-распределитель, который просто выделяет кусок памяти и возвращает части этой памяти в вызовы myalloc().

Мы заметили огромные издержки памяти при использовании malloc: на высоте использования памяти для кода malloc() требуется около 170 мегабайт памяти для изображения 1920x1080x16bpp, в то время как распределитель пула выделяет всего 48 мегабайт, из которых 47 используются программой.

В терминах шаблонов распределения памяти программа выделяет большое количество 8-байтных (большинство), 32-байтных (много) и 1080-байтовых блоков (некоторые) с тестовым изображением. Помимо этого, в коде нет динамических распределений памяти.

ОС системы тестирования - Windows 7 (64 бит).

Как мы тестировали использование памяти?

С помощью пользовательского распределителя мы можем видеть, сколько памяти используется, потому что все вызовы malloc отложены на распределитель. С помощью malloc() в режиме отладки мы просто набрали код и просмотрели использование памяти в диспетчере задач. В режиме выпуска мы делали то же самое, но менее мелкозернистым, потому что компилятор оптимизировал много материала, поэтому мы не могли пройти через кусок по частям (разница в памяти между выпуском и отладкой составляла около 20 МБ, что я бы приписывал оптимизация и отсутствие отладочной информации в режиме выпуска).

Может ли malloc быть причиной таких огромных накладных расходов? Если да, то что именно вызывает это накладные расходы внутри malloc?

Ответ 1

Во-первых, malloc выравнивает указатели до 16 байтовых границ. Кроме того, они хранят по крайней мере один указатель (или выделенную длину) в адресах, предшествующих возвращаемому значению. Затем они, вероятно, добавляют волшебное значение или счетчик выпуска, чтобы указать, что связанный список не сломан или что блок памяти не был выпущен дважды (бесплатные ASSERTS для двойных освобождений).

#include <stdlib.h>
#include <stdio.h>

int main(int ac, char**av)
{
  int *foo = malloc(4);
  int *bar = malloc(4);
  printf("%d\n", (int)bar - (int)foo);
}

Возврат: 32

Ответ 2

В Windows 7 вы всегда получите распределитель кучи низкой фрагментации, без явного вызова HeapSetInformation(), чтобы запросить его. Этот распределитель жертвует виртуальным пространством памяти, чтобы уменьшить фрагментацию. Ваша программа на самом деле не использует 170 мегабайт, вы просто видите кучу свободных блоков, лежащих вокруг, ожидая выделения аналогичного размера.

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

Лучше всего просто не беспокоиться об этом, 170 МБ - это довольно маленький картофель. И имейте в виду, что это виртуальная память, это ничего не стоит.

Ответ 3

Внимание: при запуске вашей программы в Visual Studio или при подключении любого отладчика по умолчанию поведение malloc сильно изменяется, Low Fragmentation Heap is не используется, и накладные расходы на память могут не соответствовать реальному использованию (см. также fooobar.com/info/538379/...). Вам нужно использовать переменную среды _NO_DEBUG_HEAP = 1, чтобы избежать ее попадания или измерить использование памяти, если она не работает под отладчиком.