анализ утечки памяти с помощью top

Я хотел бы использовать инструмент «сверху» для анализа потребления памяти и возможных утечек памяти в процессе. Для этого я написал эту программу (имя программы: memoryTest):

int main(){
char* q;
for(int i=0; i<100; i++){
    q = (char*) malloc(1024); 
    sleep(1);
}
return 0;   

}

Сверху теперь я могу смотреть эту программу, фильтруя с опцией «o» и спецификацией фильтра «COMMAND = memoryTest» после указанного процесса, однако я не вижу изменений в потреблении памяти процессом. У меня тут глупая ошибка?


person Andre    schedule 05.12.2018    source источник
comment
Ваша среда выполнения / ОС, вероятно, умна и фактически не выделяет память, которую вы запрашивали, но не используете. Попробуйте записать что-нибудь в память.   -  person nwp    schedule 05.12.2018
comment
Ваш компилятор выполняет оптимизацию, а вызовы malloc были удалены из окончательного кода.   -  person tunglt    schedule 05.12.2018
comment
@TungLeThanh Я не совсем уверен, является ли это правильной оптимизацией. Вы можете, даже во время выполнения, перенаправить вызовы malloc в свою собственную функцию с побочными эффектами (например, с помощью LD_PRELOAD), о которых компилятор не может знать во время компиляции. Связанное обсуждение: stackoverflow.com/q/53373421/580083   -  person Daniel Langr    schedule 05.12.2018
comment
@DanielLangr: я наблюдал поведение при компиляции данного приложения с активированным -O1 (с помощью gcc).   -  person tunglt    schedule 05.12.2018
comment
@TungLeThanh Вы правы, причина в том, что malloc - это не внешняя функция, а библиотечная функция без побочных эффектов. Таким образом, компилятору разрешено оптимизировать свои вызовы.   -  person Daniel Langr    schedule 05.12.2018
comment
Вы можете увидеть изменение потребления памяти, если вы измените размер запроса памяти на 1024*1024. На самом деле glibc использует около 128К (или 1М, в зависимости от модели) блоков для распределителя памяти. Если размер вашего запроса слишком мал, ваше приложение будет использовать доступную память до тех пор, пока не будет заполнен блок размером 128 КБ.   -  person tunglt    schedule 05.12.2018


Ответы (3)


Со страницы руководства malloc:

Обычно malloc() выделяет память из кучи и корректирует размер кучи по мере необходимости, используя sbrk(2). При выделении блоков памяти, превышающих MMAP_THRESHOLD байт, реализация glibc malloc() выделяет память как приватное анонимное отображение с помощью mmap(2). MMAP_THRESHOLD по умолчанию равен 128 КБ, но его можно изменить с помощью mallopt(3). До Linux 4.7 выделения, выполняемые с помощью mmap(2), не зависели от лимита ресурсов RLIMIT_DATA; начиная с Linux 4.7, это ограничение также применяется для распределений, выполняемых с помощью mmap(2).

Пулы памяти называются аренами, а реализация находится в arena.c. Макрос HEAP_MAX_SIZE определяет максимальный размер арены, и в основном он составляет 1 МБ для 32-разрядных и 64 МБ для 64-разрядных:

HEAP_MAX_SIZE = (2 * DEFAULT_MMAP_THRESHOLD_MAX)
32-bit [DEFAULT_MMAP_THRESHOLD_MAX = (512 * 1024)] = 1,048,576 (1MB)
64-bit [DEFAULT_MMAP_THRESHOLD_MAX = (4 * 1024 * 1024 * sizeof(long))] = 67,108,864 (64MB)

Информация из реализации кучи (arena.c):

/* Куча — это отдельная непрерывная область памяти, содержащая (объединяемые) malloc_chunks. Он выделяется с помощью mmap() и всегда начинается с адреса, выровненного с HEAP_MAX_SIZE. */

ИЗМЕНИТЬ:

Выделение кучи можно наблюдать с помощью strace. При первом вызове brk() основной области выделяется 200 КБ (72 КБ из libstdc++ с 128 КБ top_pad).

brk(NULL)                               = 0x556ecb423000 -> current program break
brk(0x556ecb455000)                     = 0x556ecb455000 -> resize the heap by moving brk 0x32000 bytes upward (main arena initialization with 200K). 
write(1, "i = 0\n", 8)                = 8
...
write(1, "i = 123\n", 8)                = 8     
brk(0x556ecb476000)                     = 0x556ecb476000 -> resize the heap by moving brk 0x21000 bytes upward (growing heap 128K). 
...
write(1, "i = 252\n", 8)                = 8
brk(0x556ecb497000)                     = 0x556ecb497000 -> resize the heap by moving brk 0x21000 bytes upward (growing heap 128K). 

Ваше приложение использовало только 100 КБ из 128 КБ доступной кучи, поэтому программа top или htop не отслеживала потребление памяти.

Вы можете легко увидеть изменение потребления памяти, если вы заставите glibc использовать mmap(), запрашивая блоки размером более 128 КБ или увеличив количество блоков (> 128).

person tunglt    schedule 05.12.2018

Попробуйте следующее:

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

int main(){
  char* q;
  for(int i=0; i<10000; i++){
    q = (char*)malloc(1024); 
    memset(q,0,1024);
  }
  getchar();
  return 0;
}

для разных значений в цикле for.

Проблема здесь в том, что Linux не обязательно использует память, даже если она выделена, пока она не будет фактически заполнена. Поэтому вам нужно записывать данные в то, что вы выделяете, иначе он может даже не зарегистрировать, что эта память используется. Вот почему некоторые приложения могут выделять память, и это нормально, но даже когда они выделили память, когда они начинают использовать память, они могут обнаружить, что память недоступна.

Memset будет принудительно записывать нули в выделенный буфер, тем самым заставляя память использоваться и регистрироваться как используемая сверху. Обратите внимание, что здесь может быть проще использовать htop.

Если вы хотите подробнее изучить «оптимистичный malloc», который является характеристикой Linux, обратите внимание, что некоторые другие операционные системы не ведут себя таким образом.

Также стоит отметить, что внутренняя память выделяется непрерывными кусками определенного размера. Таким образом, выделение, скажем, дополнительного 1 КБ может не привести к увеличению размера, если память выделена в минимальном размере блока, скажем, 4 КБ.

person Owl    schedule 05.12.2018

Если вы хотите понять использование памяти, не используйте top. Используйте бесплатную программу с открытым исходным кодом https://github.com/vmware/chap (отказ от ответственности: я оригинальный разработчик).

Просто соберите живое ядро ​​до завершения работы программы, например, запустив gcore, затем введите:

глава ваше-основное-имя-файла

Затем вы можете делать такие вещи, как:

количество просочившихся список просочившихся кол-во использованных кол-во бесплатно ....

Вот пример использования вашей программы, собирающей ядро ​​через 30 секунд:

-bash-4.1$ ./q53633998 &
[1] 18014
-bash-4.1$ sleep 30
gcore 18014
-bash-4.1$ gcore 18014
0x00000030ed6aca20 in __nanosleep_nocancel () at ../sysdeps/unix/syscall-         template.S:82
82      T_PSEUDO (SYSCALL_SYMBOL, SYSCALL_NAME, SYSCALL_NARGS)
Saved corefile core.18014
-bash-4.1$ ~/public_html/chap core.18014
chap> count used
36 allocations use 0x9120 (37,152) bytes.
chap> count free
1 allocations use 0x17db0 (97,712) bytes.
chap> count leaked
35 allocations use 0x8d18 (36,120) bytes.
chap> count anchored
1 allocations use 0x408 (1,032) bytes.
chap> list anchored
Used allocation at 19d4e40 of size 408

1 allocations use 0x408 (1,032) bytes.
chap> explain 19d4e40
Address 19d4e40 is at offset 0 of
an anchored allocation at 19d4e40 of size 408
Allocation at 19d4e40 appears to be directly anchored from at least one stack.
Address 0x7ffc88570270 is on the live part of the stack for thread 1.
Stack address 7ffc88570270 references 19d4e40

chap> list leaked
Used allocation at 19cc010 of size 408

Used allocation at 19cc420 of size 408

Used allocation at 19cc830 of size 408

Used allocation at 19ccc40 of size 408

Used allocation at 19cd050 of size 408

Used allocation at 19cd460 of size 408

Used allocation at 19cd870 of size 408

Used allocation at 19cdc80 of size 408

Used allocation at 19ce090 of size 408

Used allocation at 19ce4a0 of size 408

Used allocation at 19ce8b0 of size 408

Used allocation at 19cecc0 of size 408

Used allocation at 19cf0d0 of size 408

Used allocation at 19cf4e0 of size 408

Used allocation at 19cf8f0 of size 408

Used allocation at 19cfd00 of size 408

Used allocation at 19d0110 of size 408

Used allocation at 19d0520 of size 408

Used allocation at 19d0930 of size 408

Used allocation at 19d0d40 of size 408

Used allocation at 19d1150 of size 408

Used allocation at 19d1560 of size 408

Used allocation at 19d1970 of size 408

Used allocation at 19d1d80 of size 408

Used allocation at 19d2190 of size 408

Used allocation at 19d25a0 of size 408

Used allocation at 19d29b0 of size 408

Used allocation at 19d2dc0 of size 408

Used allocation at 19d31d0 of size 408

Used allocation at 19d35e0 of size 408

Used allocation at 19d39f0 of size 408

Used allocation at 19d3e00 of size 408

Used allocation at 19d4210 of size 408

Used allocation at 19d4620 of size 408

Used allocation at 19d4a30 of size 408

35 allocations use 0x8d18 (36,120) bytes.
chap> list free
Free allocation at 19d5250 of size 17db0

1 allocations use 0x17db0 (97,712) bytes.
chap>

Это последнее «свободное» выделение является хвостом блока памяти, который был выделен во время первого вызова malloc и постепенно разделялся по мере выполнения последующих вызовов malloc.

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

person Tim Boddy    schedule 06.12.2018