Страницы

Поиск по вопросам

понедельник, 16 декабря 2019 г.

Неочевидная утечка памяти

#cpp #linux #valgrind


Есть два сервера с идентичным ПО, железом и рабочим окружением. ОС старый добрый
6 дебиан. Разница лишь в количестве оперативки: 4 гига на тестовом и 128 на рабочем.
Есть код на C++, который обрабатывает сетевые соединения, получает данные и пишет
их в базу. При выполнении идентичного запроса на тестовом сервере использование оперативки
и виртуальной памяти держится на одном уровне, а на рабочем только растет.
Утечек памяти valgrind не нашел, структуры чисты, руками очищается даже выделяемая
"лишняя" память векторов, плюс руками же чистится куча. 
Собственно вопрос: в какую сторону еще можно копнуть, чтобы разрешить проблему?

ps ef -o vsize,rss,%mem -p pidof непалюсь 

Тестовый сервер

VSZ    RSS   %MEM 
448768 40832 0.6


Рабочий сервер

VSZ      RSS      %MEM
38446720 23682724 17.9

    


Ответы

Ответ 1



Для поиска неявных утечек можно воспользоваться другим инструментом valgrind'а: massif. Это профайлер кучи, который работает по следующему принципу: во время выполнения программы логируются все выделения/освобождения памяти, а также периодически делаются снимки использования памяти. В некоторых «детализированных» снимках сохраняется подробная статистика, сколько в каждой «точке аллокации» (функции в бектрейсе к malloc'у) было выделено, но не освобождено памяти. Логируются только точки аллокации, выделенная память из кучи к которым превосходит некий порог (по умолчанию ≥1% от общего объёма выделенного из кучи). Тестовый пример Пара контейнеров, которые равномерно заполняются и освобождаются, но в Стеке элемент «Забыли» извлечь: #include #include #include #include #include #include class Container { public: virtual void store(int s) = 0; virtual int unstore() = 0; virtual ~Container() {}; }; class Stack: public Container { std::stack data; public: virtual void store(int s) { data.push(s); } virtual int unstore() { if (data.size() !=0 ) { auto rv = data.top(); // data.pop(); return rv; } else { return std::numeric_limits::min(); } } virtual ~Stack() {}; }; class Queue: public Container { std::queue data; public: virtual void store(int s) { data.push(s); } virtual int unstore() { if (data.size() !=0 ) { auto rv = data.front(); data.pop(); return rv; } else { return std::numeric_limits::min(); } } virtual ~Queue() {}; }; int main(void) { std::unique_ptr cont1 (new Stack); std::unique_ptr cont2 (new Queue); for (int i = 0; i < 1024*1024; ++i) { int num = rand(); if (numstore(num); } else if (numunstore(); } else if (numstore(num); } else { cont2->unstore(); } } return 0; } После запуска valgrind --tool=massif ./mem_eater получаем massif.out.28861. Далее просматриваем его с помощью родной утилитки (к сожалению, сторонних графических инструментов для этого пока нет) ms_print massif.out.28861 и получаем здоровенную простынку с текстом (оставлена только одна секция для наглядности): -------------------------------------------------------------------------------- Command: ./mem_eater Massif arguments: (none) ms_print arguments: massif.out.28861 -------------------------------------------------------------------------------- MB 1.116^ # | @@@# | @@@@@@@# | @:@@@@@@@@@# | @:@:@@@@@@@@@# | ::::@:@:@@@@@@@@@# | @@@:::: :@:@:@@@@@@@@@# | @@@@@ @:::: :@:@:@@@@@@@@@# | :@@@ @@@ @:::: :@:@:@@@@@@@@@# | @:@@:@@@ @@@ @:::: :@:@:@@@@@@@@@# | ::::@:@ :@@@ @@@ @:::: :@:@:@@@@@@@@@# | :@@:: :@:@ :@@@ @@@ @:::: :@:@:@@@@@@@@@# | ::@:::@ :: :@:@ :@@@ @@@ @:::: :@:@:@@@@@@@@@# | :@:: @: :@ :: :@:@ :@@@ @@@ @:::: :@:@:@@@@@@@@@# | :::@@:@:: @: :@ :: :@:@ :@@@ @@@ @:::: :@:@:@@@@@@@@@# | ::::: :@ :@:: @: :@ :: :@:@ :@@@ @@@ @:::: :@:@:@@@@@@@@@# | @@:: ::: :@ :@:: @: :@ :: :@:@ :@@@ @@@ @:::: :@:@:@@@@@@@@@# | :::@@ :: ::: :@ :@:: @: :@ :: :@:@ :@@@ @@@ @:::: :@:@:@@@@@@@@@# | ::::: :@@ :: ::: :@ :@:: @: :@ :: :@:@ :@@@ @@@ @:::: :@:@:@@@@@@@@@# | :@: ::: :@@ :: ::: :@ :@:: @: :@ :: :@:@ :@@@ @@@ @:::: :@:@:@@@@@@@@@# 0 +----------------------------------------------------------------------->Mi 0 302.7 Number of snapshots: 64 Detailed snapshots: [2, 8, 9, 16, 17, 19, 22, 25, 30, 32, 34, 35, 36, 37, 38, 39, 40, 46, 48, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63 (peak)] -------------------------------------------------------------------------------- n time(i) total(B) useful-heap(B) extra-heap(B) stacks(B) -------------------------------------------------------------------------------- 0 0 0 0 0 0 1 5,073,918 83,184 82,976 208 0 ..... -------------------------------------------------------------------------------- n time(i) total(B) useful-heap(B) extra-heap(B) stacks(B) -------------------------------------------------------------------------------- 40 224,967,222 841,304 829,744 11,560 0 98.63% (829,744B) (heap allocation functions) malloc/new/new[], --alloc-fns, etc. ->87.51% (736,256B) 0x10AB96: __gnu_cxx::new_allocator::allocate(unsigned long, void const*) (new_allocator.h:104) | ->87.51% (736,256B) 0x10A919: std::allocator_traits >::allocate(std::allocator&, unsigned long) (alloc_traits.h:436) | ->87.51% (736,256B) 0x10A398: std::_Deque_base >::_M_allocate_node() (stl_deque.h:600) | ->87.45% (735,744B) 0x109D4C: void std::deque >::_M_push_back_aux(int const&) (deque.tcc:471) | | ->87.45% (735,744B) 0x109875: std::deque >::push_back(int const&) (stl_deque.h:1527) | | ->87.03% (732,160B) 0x109499: std::stack > >::push(int const&) (stl_stack.h:219) | | | ->87.03% (732,160B) 0x10912E: Stack::store(int) (mem_eater.cpp:19) | | | ->87.03% (732,160B) 0x108F11: main (mem_eater.cpp:55) | | | | | ->00.43% (3,584B) in 1+ places, all below ms_print's threshold (01.00%) | | | ->00.06% (512B) in 1+ places, all below ms_print's threshold (01.00%) | ->08.64% (72,704B) 0x4EC16EE: ??? (in /usr/lib64/gcc/x86_64-pc-linux-gnu/6.4.0/libstdc++.so.6.0.22) | ->08.64% (72,704B) 0x400F448: call_init.part.0 (in /lib64/ld-2.25.so) | ->08.64% (72,704B) 0x400F559: _dl_init (in /lib64/ld-2.25.so) | ->08.64% (72,704B) 0x4000B88: ??? (in /lib64/ld-2.25.so) | ->02.45% (20,608B) 0x10AC08: __gnu_cxx::new_allocator::allocate(unsigned long, void const*) (new_allocator.h:104) | ->02.45% (20,608B) 0x10A9DB: std::allocator_traits >::allocate(std::allocator&, unsigned long) (alloc_traits.h:436) | ->02.45% (20,608B) 0x10A566: std::_Deque_base >::_M_allocate_map(unsigned long) (stl_deque.h:614) | ->02.45% (20,608B) 0x10A81A: std::deque >::_M_reallocate_map(unsigned long, bool) (deque.tcc:929) | | ->02.45% (20,608B) 0x10A36D: std::deque >::_M_reserve_map_at_back(unsigned long) (stl_deque.h:2116) | | ->02.45% (20,608B) 0x109D34: void std::deque >::_M_push_back_aux(int const&) (deque.tcc:470) | | ->02.45% (20,608B) 0x109875: std::deque >::push_back(int const&) (stl_deque.h:1527) | | ->02.43% (20,464B) 0x109499: std::stack > >::push(int const&) (stl_stack.h:219) | | | ->02.43% (20,464B) 0x10912E: Stack::store(int) (mem_eater.cpp:19) | | | ->02.43% (20,464B) 0x108F11: main (mem_eater.cpp:55) | | | | | ->00.02% (144B) in 1+ places, all below ms_print's threshold (01.00%) | | | ->00.00% (0B) in 1+ places, all below ms_print's threshold (01.00%) | ->00.02% (176B) in 1+ places, all below ms_print's threshold (01.00%) -------------------------------------------------------------------------------- n time(i) total(B) useful-heap(B) extra-heap(B) stacks(B) -------------------------------------------------------------------------------- 41 230,069,860 857,944 846,128 11,816 0 42 235,254,410 875,624 863,536 12,088 0 43 240,656,059 892,784 880,432 12,352 0 44 246,176,028 911,504 898,864 12,640 0 45 251,968,045 931,264 918,320 12,944 0 46 258,089,356 952,584 939,312 13,272 0 98.61% (939,312B) (heap allocation functions) malloc/new/new[], --alloc-fns, etc. ->88.79% (845,824B) 0x10AB96: __gnu_cxx::new_allocator::allocate(unsigned long, void const*) (new_allocator.h:104) | ->88.79% (845,824B) 0x10A919: std::allocator_traits >::allocate(std::allocator&, unsigned long) (alloc_traits.h:436) | ->88.79% (845,824B) 0x10A398: std::_Deque_base >::_M_allocate_node() (stl_deque.h:600) | ->88.74% (845,312B) 0x109D4C: void std::deque >::_M_push_back_aux(int const&) (deque.tcc:471) | | ->88.74% (845,312B) 0x109875: std::deque >::push_back(int const&) (stl_deque.h:1527) | | ->88.47% (842,752B) 0x109499: std::stack > >::push(int const&) (stl_stack.h:219) | | | ->88.47% (842,752B) 0x10912E: Stack::store(int) (mem_eater.cpp:19) | | | ->88.47% (842,752B) 0x108F11: main (mem_eater.cpp:55) | | | | | ->00.27% (2,560B) in 1+ places, all below ms_print's threshold (01.00%) | | | ->00.05% (512B) in 1+ places, all below ms_print's threshold (01.00%) | ->07.63% (72,704B) 0x4EC16EE: ??? (in /usr/lib64/gcc/x86_64-pc-linux-gnu/6.4.0/libstdc++.so.6.0.22) | ->07.63% (72,704B) 0x400F448: call_init.part.0 (in /lib64/ld-2.25.so) | ->07.63% (72,704B) 0x400F559: _dl_init (in /lib64/ld-2.25.so) | ->07.63% (72,704B) 0x4000B88: ??? (in /lib64/ld-2.25.so) | ->02.16% (20,608B) 0x10AC08: __gnu_cxx::new_allocator::allocate(unsigned long, void const*) (new_allocator.h:104) | ->02.16% (20,608B) 0x10A9DB: std::allocator_traits >::allocate(std::allocator&, unsigned long) (alloc_traits.h:436) | ->02.16% (20,608B) 0x10A566: std::_Deque_base >::_M_allocate_map(unsigned long) (stl_deque.h:614) | ->02.16% (20,608B) 0x10A81A: std::deque >::_M_reallocate_map(unsigned long, bool) (deque.tcc:929) | | ->02.16% (20,608B) 0x10A36D: std::deque >::_M_reserve_map_at_back(unsigned long) (stl_deque.h:2116) | | ->02.16% (20,608B) 0x109D34: void std::deque >::_M_push_back_aux(int const&) (deque.tcc:470) | | ->02.16% (20,608B) 0x109875: std::deque >::push_back(int const&) (stl_deque.h:1527) | | ->02.15% (20,464B) 0x109499: std::stack > >::push(int const&) (stl_stack.h:219) | | | ->02.15% (20,464B) 0x10912E: Stack::store(int) (mem_eater.cpp:19) | | | ->02.15% (20,464B) 0x108F11: main (mem_eater.cpp:55) | | | | | ->00.02% (144B) in 1+ places, all below ms_print's threshold (01.00%) | | | ->00.00% (0B) in 1+ places, all below ms_print's threshold (01.00%) | ->00.02% (176B) in 1+ places, all below ms_print's threshold (01.00%) ..... Из графика видно, что использование памяти для данного игрушечного примера постоянно растёт. А из последующего отчёта видно, что ко времени снимка 46 для хранения стека уже используется (845,824+20,608) байт, или же больше 90% всей кучи используемой программой, что указывает на очевидную ошибку. Интерпретация вывода не механическая и требует некоторых усилий и сноровки, но это помогает увидеть на что стоит обратить внимание. Если поправить злосчастный комментарий, то получается практически чистый вывод с несколькими ложноположительными срабатываниями связанными с простым пиковым потреблением памяти: -------------------------------------------------------------------------------- Command: ./mem_eater Massif arguments: (none) ms_print arguments: massif.out.29399 -------------------------------------------------------------------------------- KB 77.60^ #:: : : | :@:::::::::::::::::::::::#: :::::::::::::::::::::::@::::::::@:::: | :::::::@::: ::: :: ::::: :::: :#: :: :::::::: :: :::::: :@::::::::@:::: | :: :: :@::: ::: :: ::::: :::: :#: :: :::::::: :: :::::: :@::::::::@:::: | :: :: :@::: ::: :: ::::: :::: :#: :: :::::::: :: :::::: :@::::::::@:::: | :: :: :@::: ::: :: ::::: :::: :#: :: :::::::: :: :::::: :@::::::::@:::: | :: :: :@::: ::: :: ::::: :::: :#: :: :::::::: :: :::::: :@::::::::@:::: | :: :: :@::: ::: :: ::::: :::: :#: :: :::::::: :: :::::: :@::::::::@:::: | :: :: :@::: ::: :: ::::: :::: :#: :: :::::::: :: :::::: :@::::::::@:::: | :: :: :@::: ::: :: ::::: :::: :#: :: :::::::: :: :::::: :@::::::::@:::: | :: :: :@::: ::: :: ::::: :::: :#: :: :::::::: :: :::::: :@::::::::@:::: | :: :: :@::: ::: :: ::::: :::: :#: :: :::::::: :: :::::: :@::::::::@:::: | :: :: :@::: ::: :: ::::: :::: :#: :: :::::::: :: :::::: :@::::::::@:::: | :: :: :@::: ::: :: ::::: :::: :#: :: :::::::: :: :::::: :@::::::::@:::: | :: :: :@::: ::: :: ::::: :::: :#: :: :::::::: :: :::::: :@::::::::@:::: | :: :: :@::: ::: :: ::::: :::: :#: :: :::::::: :: :::::: :@::::::::@:::: | :: :: :@::: ::: :: ::::: :::: :#: :: :::::::: :: :::::: :@::::::::@:::: | :: :: :@::: ::: :: ::::: :::: :#: :: :::::::: :: :::::: :@::::::::@:::: | :: :: :@::: ::: :: ::::: :::: :#: :: :::::::: :: :::::: :@::::::::@:::: | :: :: :@::: ::: :: ::::: :::: :#: :: :::::::: :: :::::: :@::::::::@:::: 0 +----------------------------------------------------------------------->Mi 0 319.7 Number of snapshots: 66 Detailed snapshots: [7, 26 (peak), 50, 60] -------------------------------------------------------------------------------- n time(i) total(B) useful-heap(B) extra-heap(B) stacks(B) -------------------------------------------------------------------------------- 0 0 0 0 0 0 1 6,357,999 74,624 74,544 80 0 2 11,037,520 74,104 74,032 72 0 3 18,736,364 74,104 74,032 72 0 4 23,440,137 74,104 74,032 72 0 5 27,510,682 74,104 74,032 72 0 6 33,147,787 75,664 75,568 96 0 7 38,465,295 75,664 75,568 96 0 99.87% (75,568B) (heap allocation functions) malloc/new/new[], --alloc-fns, etc. ->96.09% (72,704B) 0x4EC16EE: ??? (in /usr/lib64/gcc/x86_64-pc-linux-gnu/6.4.0/libstdc++.so.6.0.22) | ->96.09% (72,704B) 0x400F448: call_init.part.0 (in /lib64/ld-2.25.so) | ->96.09% (72,704B) 0x400F559: _dl_init (in /lib64/ld-2.25.so) | ->96.09% (72,704B) 0x4000B88: ??? (in /lib64/ld-2.25.so) | ->03.38% (2,560B) 0x10AC9E: __gnu_cxx::new_allocator::allocate(unsigned long, void const*) (new_allocator.h:104) | ->03.38% (2,560B) 0x10AA21: std::allocator_traits >::allocate(std::allocator&, unsigned long) (alloc_traits.h:436) | ->03.38% (2,560B) 0x10A4A0: std::_Deque_base >::_M_allocate_node() (stl_deque.h:600) | ->02.71% (2,048B) 0x109DD6: void std::deque >::_M_push_back_aux(int const&) (deque.tcc:471) | | ->02.71% (2,048B) 0x1098A1: std::deque >::push_back(int const&) (stl_deque.h:1527) | | ->02.03% (1,536B) 0x1095AF: std::queue > >::push(int const&) (stl_queue.h:243) | | | ->02.03% (1,536B) 0x109232: Queue::store(int) (mem_eater.cpp:35) | | | ->02.03% (1,536B) 0x108F5E: main (mem_eater.cpp:59) | | | | | ->00.68% (512B) in 1+ places, all below ms_print's threshold (01.00%) | | | ->00.68% (512B) in 1+ places, all below ms_print's threshold (01.00%) | ->00.40% (304B) in 1+ places, all below ms_print's threshold (01.00%) -------------------------------------------------------------------------------- n time(i) total(B) useful-heap(B) extra-heap(B) stacks(B) -------------------------------------------------------------------------------- 8 43,466,034 75,664 75,568 96 0 9 50,687,976 75,744 75,648 96 0 10 54,674,907 76,784 76,672 112 0 11 61,489,426 76,784 76,672 112 0 12 66,913,719 76,784 76,672 112 0 13 73,503,124 76,784 76,672 112 0 14 80,840,510 75,744 75,648 96 0 15 85,776,021 75,744 75,648 96 0 16 93,894,556 76,784 76,672 112 0 17 98,955,219 76,264 76,160 104 0 18 104,342,732 76,784 76,672 112 0 19 109,566,108 75,744 75,648 96 0 20 116,286,750 76,784 76,672 112 0 21 124,342,177 76,784 76,672 112 0 22 128,350,479 77,824 77,696 128 0 23 133,653,415 76,264 76,160 104 0 24 139,392,895 77,824 77,696 128 0 25 144,750,550 78,864 78,720 144 0 26 151,838,741 79,464 79,312 152 0 99.81% (79,312B) (heap allocation functions) malloc/new/new[], --alloc-fns, etc. ->91.49% (72,704B) 0x4EC16EE: ??? (in /usr/lib64/gcc/x86_64-pc-linux-gnu/6.4.0/libstdc++.so.6.0.22) | ->91.49% (72,704B) 0x400F448: call_init.part.0 (in /lib64/ld-2.25.so) | ->91.49% (72,704B) 0x400F559: _dl_init (in /lib64/ld-2.25.so) | ->91.49% (72,704B) 0x4000B88: ??? (in /lib64/ld-2.25.so) | ->07.73% (6,144B) 0x10AC9E: __gnu_cxx::new_allocator::allocate(unsigned long, void const*) (new_allocator.h:104) | ->07.73% (6,144B) 0x10AA21: std::allocator_traits >::allocate(std::allocator&, unsigned long) (alloc_traits.h:436) | ->07.73% (6,144B) 0x10A4A0: std::_Deque_base >::_M_allocate_node() (stl_deque.h:600) | ->07.09% (5,632B) 0x109DD6: void std::deque >::_M_push_back_aux(int const&) (deque.tcc:471) | | ->07.09% (5,632B) 0x1098A1: std::deque >::push_back(int const&) (stl_deque.h:1527) | | ->03.87% (3,072B) 0x1095AF: std::queue > >::push(int const&) (stl_queue.h:243) | | | ->03.87% (3,072B) 0x109232: Queue::store(int) (mem_eater.cpp:35) | | | ->03.87% (3,072B) 0x108F5E: main (mem_eater.cpp:59) | | | | | ->03.22% (2,560B) 0x1094A9: std::stack > >::push(int const&) (stl_stack.h:219) | | ->03.22% (2,560B) 0x10912E: Stack::store(int) (mem_eater.cpp:19) | | ->03.22% (2,560B) 0x108F11: main (mem_eater.cpp:55) | | | ->00.64% (512B) in 1+ places, all below ms_print's threshold (01.00%) | ->00.58% (464B) in 1+ places, all below ms_print's threshold (01.00%) ..... -------------------------------------------------------------------------------- n time(i) total(B) useful-heap(B) extra-heap(B) stacks(B) -------------------------------------------------------------------------------- 61 318,903,255 77,384 77,264 120 0 62 322,953,503 77,384 77,264 120 0 63 327,012,737 77,384 77,264 120 0 64 331,088,164 76,864 76,752 112 0 65 335,212,302 76,864 76,752 112 0 Другие утилиты Кроме massif в valgrind'е есть другая схожая утилита: DHAT (exp-dhat). Она работает несколько на другом принципе: для каждой точки аллокации она собирает статистику по количеству аллокаций/деаллокаций, чтениям/записям, а также времени жизни аллокаций. Её также возможно использовать для неявных утечек, но ИМХО вывод оной интерпретировать сложнее и её лучше оставить для поиска более тонких ошибок/оптимизаций: поиска сохраняемых, но не читаемых данных, оптимизаций избыточной аллокации, поиск выделений слишком больших блоков итп.

Комментариев нет:

Отправить комментарий