Отслеживание аллокаций. Malloc tracer
Анализ производительности приложений включает не только поиск узких мест в вычислительной части на CPU, но и оптимизацию использования памяти, операций с диском и сетью, минимизацию времени выполнения и др.
В этой статье я расскажу о поиске проблем потребления памяти и о проекте Malloc_tracer, разработанном для отслеживания аллокаций.
Возможные сценарии анализа
Для улучшения профиля потребления памяти важно понимать, где и для чего выделяется память.
При воспроизводимом сценарии можно применить утилиту heaptrack
, и увидеть стек вызовов для аллокаций. Но сценарии не всегда воспроизводятся.
Если на руках только боевой дамп от пользователя, то будут полезны инструменты вроде chap
и Core Analyzer
. Хотя они не дадут полной информации о месте создания аллокаций, но можно это сделать косвенно, определив тип данных в аллокациях.
Предположим, что случай воспроизводимый, но есть ограничение - приложение использует FANOTIFY
для контроля файловых операций. В этом случае GDB и heaptrack не смогут работать: их подсоединение заблокирует приложение, ожидая разрешения на файловую операцию от этого приложения.
Тогда стоит изменить подход: модифицировать аллокатор так, чтобы он сохранял информацию о вызове malloc
или new
рядом с выделенной памятью. Это позволит, сняв дамп, понять, откуда взялась каждая конкретная аллокация. Так появился проект Malloc_tracer.
Описание Malloc_tracer
Основная идея Malloc_tracer проста: добавить к каждой аллокации 16 байт с адресом возврата и размером запрошенной памяти. Адрес возврата вызова malloc
или new
можно получить с помощью __builtin_return_address(0)
. Полный стек вызова здесь излишен, часто достаточно знать лишь одно место вызова.
Если положить размер и адрес после данных пользователя, то по дампу нельзя будет извлечь эту информацию. Связано это с тем, что стандартный malloc выделяет чаще всего больше памяти, чем попросят, и кладет именно этот размер в заголовок аллокации. Информация о размере, который нужен пользователю не сохраняется.
Для получения размера реально выделенной памяти можно использовать функцию malloc_usable_size(ptr)
. Тогда в дампе можно будет однозначно найти сохраненный адрес.
Плюсы решения:
- Простота реализации.
- Небольшой оверхед (дополнительные 16 байт практически не влияют на большие аллокации).
- Возможность найти место создания каждой аллокации.
- Не нужно подключаться к процессу.
Минусы:
- На маленьких аллокациях размер служебной информации сопоставим с размером аллокации.
- Требуется использование LD_PRELOAD.
Инструменты для анализа памяти
Malloc_tracer обеспечивает лёгкий способ записи информации о выделениях памяти при невозможности подключиться к процессу. Это полезное дополнение к уже существующим инструментам:
- chap - утилита от разработчиков VMWare, позволяющая анализировать дампы без символов. Отличительная черта инструмента - для анализа не нужно изменять поведение программы для анализа. Из недостатоков отмечу, что в открытом доступе поддержан малый список аллокаторов. Я его использую для поиска аллокаций в дампе.
- Core Analyzer - мощная штука, которая основана на ванильном GDB. Требует символьной информации, но поддерживающий широкий список аллокаторов.
- heaptrack - профайлер памяти от KDE. Позволяет строить flamegraph, есть GUI. Нужно аттачиться к запущенному процессу.
- gdb-heap - плагин для GDB для отслеживания аллокаций в дампах, написан на python. Использовал для вдохновения и создания своего плагина для GDB.
- ebpf - швейцарский нож в мире Linux. С помощью него можно любые события, включая аллокации памяти.
- jemalloc, jeprof - аллокатор с поддержкой профилирования. Использует статистическое сэмплирование, чтобы минимизировать оверхед, сохраняя при этом стек вызова. Можно посмотреть и на другие аллокаторы.
- Valgrind (Massif + Memcheck) - мощный, но медленный инструмент для отслеживания аллокаций.
heaptrack
в целом предпочтительнее, если не нужно анализировать память на стеке - Massif это может. - ASan (AddressSanitizer) - не профайлер, а санитайзер от llvm. Помогает обнаруживать утечки памяти, ошибки use-after-free и обращения к освобожденной памяти. Отличается высокой скоростью.