Что лучше для выделения памяти: brk или mmap?

Необходимо реализовать динамическое распределение памяти, то бишь собственные malloc() и realloc(). Разобрался с логикой и теперь никак не могу решить, какой системный вызов лучше использовать: brk или mmap.

Brk изменяет размер кучи. Крайне прост в использовании. Но вот для realloc() придётся иногда ещё собственноручно копировать память.

Mmap отображает файл (или, с дескриптором -1, можно просто блок данных) на память и возвращает указатель на эту самую память. Получаем удобства в виде системных вызовов munmap (отвязать) и mremap (изменить размер существующего; система также выполнит копирование при необходимости).

Резонный вопрос: кого использовать? Основными факторами выступают быстродействие и эффективность (выделить как можно больше из доступной памяти).

Думается мне, (предположение) реализация brk проще, и он оттого шустрее, но ведь запрашивает непрерывную область памяти — загвоздка: ежели на пути встретится какая-нибудь память, используемая другими процессами, надо будет либо её сдвинуть, либо попросту отказать в предоставлении памяти. И тут кажется, что mmap побеждает, так как выполняет именно то, что нужно: ищет куски нужного размера и даёт на них указатель. Пишут также, что brk устарел.

Может, кто сталкивался. Есть кардинальное отличие? Предпочтительнее ли использовать одно взамен другого? Почему?


Ответы (1 шт):

Автор решения: Pak Uula

Предупреждаю сразу: это всего лишь теоретические измышления. Сам я писал аллокатор только когда проходил курс по операционным системам, и тот аллокатор был наипростейшим - односвязный список занятых и свободных блоков с аллокацией первого подходящего. Для такого тривиального аллокатора sbrk за глаза. Он хорош как учебное задание, и плох для реальной жизни: есть риск фрагментации памяти, и практически 100% гарантия, что процесс не будет возвращать память операционной системе.

В реальной жизни все используют mmap, даже самые маленькие:

Разумеется, glibc использует mmap

Но ИМХО, выбор между mmap и sbrk для аллокатора сугубо вторичен. Можно и с mmap такого наворотить, что память быстро закончится.

В своей уже классической статье Дуг Ли (Doug Lea) выделяет несколько требований к аллокаторам:

Minimizing Space: The allocator should not waste space: It should obtain as little memory from the system as possible, and should maintain memory in ways that minimize fragmentation -- ``holes''in contiguous chunks of memory that are not used by the program.

Minimizing Time: The malloc(), free() and realloc routines should be as fast as possible in the average case.

Maximizing Tunability: Optional features and behavior should be controllable by users either statically (via #define and the like) or dynamically (via control commands such as mallopt).

Maximizing Locality: Allocating chunks of memory that are typically used together near each other. This helps minimize page and cache misses during program execution.

Maximizing Error Detection: It does not seem possible for a general-purpose allocator to also serve as general-purpose memory error testing tool such as Purify. However, allocators should provide some means for detecting corruption due to overwriting memory, multiple frees, and so on.

Minimizing Anomalies: An allocator configured using default settings should perform well across a wide range of real loads that depend heavily on dynamic allocation -- windowing toolkits, GUI applications, compilers, interpretors, development tools, network (packet)-intensive programs, graphics-intensive packages, web browsers, string-processing applications, and so on.

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

Что же касается собственно вашего вопроса, то mmap даёт всё, что можно получить от sbrk, и ещё немного сверху. Чем хорош mmap:

  • можно сделать несколько независимых пулов. Например, пул для маленьких объектов очень удобен для борьбы с фрагментацией, чтобы не смешивать мелкие объекты с большими. Плюс возможны независимые аллокации для разных потоков (threads) - как минимум, можно автоматически подобрать всю висящую память после завершения потока
  • для больших объектов можно сразу аллоцировать память из операционной системы, и вернуть её обратно после освобождения объекта
→ Ссылка