glibc’s memory allocator

malloc_never_fails_tshirt-r8bdfeac6456e4703803a0d7bb91679da_va6lr_512
Когда приложение интенсивно создаёт и удаляет различные объекты при этом не производя над ними каких-либо ресурсоёмких вычислений, производительность приложения ограничивается в основном подсистемой управления памятью. И для более аккуратной разработки приложения необходимо воспринимать эту подсистему уже не как «чёрный ящик», а уже учитывать некоторые его особенности. Рассмотрим аллокатор из библиотеки glibc на платформе linux x86_64.

Какое же API предоставляет аллокатор из glibc? Как он работает? Какие неожиданности в себе таит?
Начнём с API.
Кроме malloc и free, которые известны всем, есть ещё:

  • calloc, который инициализирует выделяемую память 0ми. Что выполняет ещё одну функцию(зависит от реализации): реальное обращение к выделенным страницам, что не позволяет их выделять «лениво», что уменьшает вероятность возникновения Out Of Memory ситуаций;
  • cfree — функция добавленная для совместимости с SunOS. Является синонимом free(использовать её не рекомендуется)
  • memalign — выделить память с выравниванием
  • valloc — выделить память с выравниванием по страницам
  • pvalloc — выделить память размером кратным странице с выравниванием по страницам
  • mallinfo — возвращает структуру, содержащую информацию о состоянии аллокатора
  • mallopt — позволяет настраивать поведение аллокатора
  • malloc_trim — явный запрос на возвращение системе памяти, которая может быть возвращена
  • malloc_usable_size — возвращает размер выделенной области по указателю. Бывает полезна, когда размер неизвестен(после вызова strdup например) или недоступен(неудачно спроектированный интерфейс).
  • malloc_stats — выводит статистику выделенной памяти в STDERR. Очень полезна для отладки.
  • malloc_info — выводит состояние аллокатора в xml формате. Для ужедневного применения уж слишком подробно ;-)
  • malloc_get_state и malloc_set_state — сохранение/восстановление состояния аллокатора.

Как вообще работает malloc?
Приложению доступен довольно скудный набор системных вызовов: brk, mmap, munmap, mremap.
mmap отображает файл в виртуальное адресное пространство процесса, при этом отображение может быть как приватным так и публичным, а файл и вовсе может отсутствовать. Именно сценарий приватного отображения несуществующего файла и используется в malloc.
Получается, что приложению доступно несколько линейных адресных пространств: сегмент данных(размер которого определяется системным вызовом brk) и отображения созданный с помощью вызова mmap. Чем разнообразнее размер блоков выделяемых в одном линейном адресном пространстве, тем выше фрагментация памяти.

Как malloc работает в glibc?
Поведение аллокатора при каждом конкретном вызове malloc(и прочих) зависит от размера запрашиваемой области.

  • для запросов в 0..64 байт это кэширующий аллокатор
  • для запросов в 65..511 байт это гибридный(кэширующий+best-fit) аллокатор
  • для запросов в 512..131048 байт это best-fit аллокатор
  • для запросов в 131049..inf байт память выделяется с помощью системного вызова mmap

Значения этих границ позволяет менять функция mallopt. Например M_MMAP_THRESHOLD задаёт границу с которой начинает использоваться mmap.

Что неожиданного несёт в себе аллокатор из glibc?

  • malloc(0) выполняется так же успешно как и malloc(1) и возвращает указатель на область памяти. malloc_usable_size при этом сообщает, что 24 байта в выделенном адресном пространстве можно использовать. Во многих альтернативных аллокаторах malloc(0) всегда возвращает NULL
  • malloc возвращает NULL ещё реже чем хотелось бы. Немного зависит от конфигурации OS, но успешно выделить себе большой участок памяти и быть убитым Out Of Memory killer’ом при первом обращении к «лениво» выделинным страницам без возможность это как-либо обработать довольно просто. Например, следующий код:

    на машине с 16 ГБ памяти и без swap’а выводит что-то вроде:
  • malloc не возвращает память системе при вызовах free у объектов меньше 128 Байт. Для явного освобождения ненужной памяти можно вызывать malloc_trim, например после удаления большого списка или дерева с довольно маленькими элементами.
  • из-за различных механизмов выделения памяти разных объёмов могут сложиться ситуации, когда выделение 127КБ выполнится с ошибкой, а 129КБ выполнится успешно.
Yuriy Nazarov on GithubYuriy Nazarov on Twitter
Yuriy Nazarov
Software engineer
Люблю machine learning