Понимание виртуальной памяти Linux: вывод массива valgrind показывает существенные различия с --pages-as-heap и без него - PullRequest
0 голосов
/ 27 сентября 2018

Я прочитал документацию по этому параметру, но разница действительно огромная!Когда эта функция включена, использование памяти простой программой (см. Ниже) составляет около 7 ГБ , а когда она отключена, зарегистрированное использование составляет около 160 КБ .

top также показывает около 7 ГБ, что подтверждает результат с pages-as-heap=yes.

(У меня есть теория, но я не верю, что она могла бы объяснить такую ​​огромную разницу, поэтому - прося помощи).

Что меня особенно беспокоит, так это то, что большинствоstd::string используется память, в то время как what? никогда не печатается (имеется в виду - фактическая емкость довольно мала).

Мне нужно использовать pages-as-heap=yes при профилировании моего приложения, япросто интересно, как избежать «ложных срабатываний»


Фрагмент кода:

#include <iostream>
#include <thread>
#include <vector>
#include <chrono>

void run()
{
    while (true)
    {
        std::string s;
        s += "aaaaa";
        s += "aaaaaaaaaaaaaaa";
        s += "bbbbbbbbbb";
        s += "cccccccccccccccccccccccccccccccccccccccccccccccccc";
        if (s.capacity() > 1024) std::cout << "what?" << std::endl;

        std::this_thread::sleep_for(std::chrono::seconds(1));
    }
}

int main()
{
    std::vector<std::thread> workers;
    for( unsigned i = 0; i < 192; ++i ) workers.push_back(std::thread(&run));

    workers.back().join();
}

Скомпилировано с: g++ --std=c++11 -fno-inline -g3 -pthread

С pages-as-heap=yes:

100.00% (7,257,714,688B) (page allocation syscalls) mmap/mremap/brk, --alloc-fns, etc.
->99.75% (7,239,757,824B) 0x54E0679: mmap (mmap.c:34)
| ->53.63% (3,892,314,112B) 0x545C3CF: new_heap (arena.c:438)
| | ->53.63% (3,892,314,112B) 0x545CC1F: arena_get2.part.3 (arena.c:646)
| |   ->53.63% (3,892,314,112B) 0x5463248: malloc (malloc.c:2911)
| |     ->53.63% (3,892,314,112B) 0x4CB7E76: operator new(unsigned long) (in /usr/lib/x86_64-linux-gnu/libstdc++.so.6.0.21)
| |       ->53.63% (3,892,314,112B) 0x4CF8E37: std::string::_Rep::_S_create(unsigned long, unsigned long, std::allocator<char> const&) (in /usr/lib/x86_64-linux-gnu/libstdc++.so.6.0.21)
| |         ->53.63% (3,892,314,112B) 0x4CF9C69: std::string::_Rep::_M_clone(std::allocator<char> const&, unsigned long) (in /usr/lib/x86_64-linux-gnu/libstdc++.so.6.0.21)
| |           ->53.63% (3,892,314,112B) 0x4CF9D22: std::string::reserve(unsigned long) (in /usr/lib/x86_64-linux-gnu/libstdc++.so.6.0.21)
| |             ->53.63% (3,892,314,112B) 0x4CF9FB1: std::string::append(char const*, unsigned long) (in /usr/lib/x86_64-linux-gnu/libstdc++.so.6.0.21)
| |               ->53.63% (3,892,314,112B) 0x401252: run() (test.cpp:11)
| |                 ->53.63% (3,892,314,112B) 0x403929: void std::_Bind_simple<void (*())()>::_M_invoke<>(std::_Index_tuple<>) (functional:1700)
| |                   ->53.63% (3,892,314,112B) 0x403864: std::_Bind_simple<void (*())()>::operator()() (functional:1688)
| |                     ->53.63% (3,892,314,112B) 0x4037D2: std::thread::_Impl<std::_Bind_simple<void (*())()> >::_M_run() (thread:115)
| |                       ->53.63% (3,892,314,112B) 0x4CE2C7E: ??? (in /usr/lib/x86_64-linux-gnu/libstdc++.so.6.0.21)
| |                         ->53.63% (3,892,314,112B) 0x51C96B8: start_thread (pthread_create.c:333)
| |                           ->53.63% (3,892,314,112B) 0x54E63DB: clone (clone.S:109)
| |                             
| ->35.14% (2,550,136,832B) 0x545C35B: new_heap (arena.c:427)
| | ->35.14% (2,550,136,832B) 0x545CC1F: arena_get2.part.3 (arena.c:646)
| |   ->35.14% (2,550,136,832B) 0x5463248: malloc (malloc.c:2911)
| |     ->35.14% (2,550,136,832B) 0x4CB7E76: operator new(unsigned long) (in /usr/lib/x86_64-linux-gnu/libstdc++.so.6.0.21)
| |       ->35.14% (2,550,136,832B) 0x4CF8E37: std::string::_Rep::_S_create(unsigned long, unsigned long, std::allocator<char> const&) (in /usr/lib/x86_64-linux-gnu/libstdc++.so.6.0.21)
| |         ->35.14% (2,550,136,832B) 0x4CF9C69: std::string::_Rep::_M_clone(std::allocator<char> const&, unsigned long) (in /usr/lib/x86_64-linux-gnu/libstdc++.so.6.0.21)
| |           ->35.14% (2,550,136,832B) 0x4CF9D22: std::string::reserve(unsigned long) (in /usr/lib/x86_64-linux-gnu/libstdc++.so.6.0.21)
| |             ->35.14% (2,550,136,832B) 0x4CF9FB1: std::string::append(char const*, unsigned long) (in /usr/lib/x86_64-linux-gnu/libstdc++.so.6.0.21)
| |               ->35.14% (2,550,136,832B) 0x401252: run() (test.cpp:11)
| |                 ->35.14% (2,550,136,832B) 0x403929: void std::_Bind_simple<void (*())()>::_M_invoke<>(std::_Index_tuple<>) (functional:1700)
| |                   ->35.14% (2,550,136,832B) 0x403864: std::_Bind_simple<void (*())()>::operator()() (functional:1688)
| |                     ->35.14% (2,550,136,832B) 0x4037D2: std::thread::_Impl<std::_Bind_simple<void (*())()> >::_M_run() (thread:115)
| |                       ->35.14% (2,550,136,832B) 0x4CE2C7E: ??? (in /usr/lib/x86_64-linux-gnu/libstdc++.so.6.0.21)
| |                         ->35.14% (2,550,136,832B) 0x51C96B8: start_thread (pthread_create.c:333)
| |                           ->35.14% (2,550,136,832B) 0x54E63DB: clone (clone.S:109)
| |                             
| ->10.99% (797,306,880B) 0x51CA1D4: pthread_create@@GLIBC_2.2.5 (allocatestack.c:513)
|   ->10.99% (797,306,880B) 0x4CE2DC1: std::thread::_M_start_thread(std::shared_ptr<std::thread::_Impl_base>, void (*)()) (in /usr/lib/x86_64-linux-gnu/libstdc++.so.6.0.21)
|     ->10.99% (797,306,880B) 0x4CE2ECB: std::thread::_M_start_thread(std::shared_ptr<std::thread::_Impl_base>) (in /usr/lib/x86_64-linux-gnu/libstdc++.so.6.0.21)
|       ->10.99% (797,306,880B) 0x401BEA: std::thread::thread<void (*)()>(void (*&&)()) (thread:138)
|         ->10.99% (797,306,880B) 0x401353: main (test.cpp:24)
|           
->00.25% (17,956,864B) in 1+ places, all below ms_print's threshold (01.00%)

при использовании pages-as-heap=no:

96.38% (159,289B) (heap allocation functions) malloc/new/new[], --alloc-fns, etc.
->43.99% (72,704B) 0x4EBAEFE: ??? (in /usr/lib/x86_64-linux-gnu/libstdc++.so.6.0.21)
| ->43.99% (72,704B) 0x40106B8: call_init.part.0 (dl-init.c:72)
|   ->43.99% (72,704B) 0x40107C9: _dl_init (dl-init.c:30)
|     ->43.99% (72,704B) 0x4000C68: ??? (in /lib/x86_64-linux-gnu/ld-2.23.so)
|       
->33.46% (55,296B) 0x40138A3: _dl_allocate_tls (dl-tls.c:322)
| ->33.46% (55,296B) 0x53D126D: pthread_create@@GLIBC_2.2.5 (allocatestack.c:588)
|   ->33.46% (55,296B) 0x4EE9DC1: std::thread::_M_start_thread(std::shared_ptr<std::thread::_Impl_base>, void (*)()) (in /usr/lib/x86_64-linux-gnu/libstdc++.so.6.0.21)
|     ->33.46% (55,296B) 0x4EE9ECB: std::thread::_M_start_thread(std::shared_ptr<std::thread::_Impl_base>) (in /usr/lib/x86_64-linux-gnu/libstdc++.so.6.0.21)
|       ->33.46% (55,296B) 0x401BEA: std::thread::thread<void (*)()>(void (*&&)()) (thread:138)
|         ->33.46% (55,296B) 0x401353: main (test.cpp:24)
|           
->12.12% (20,025B) 0x4EFFE37: std::string::_Rep::_S_create(unsigned long, unsigned long, std::allocator<char> const&) (in /usr/lib/x86_64-linux-gnu/libstdc++.so.6.0.21)
| ->12.12% (20,025B) 0x4F00C69: std::string::_Rep::_M_clone(std::allocator<char> const&, unsigned long) (in /usr/lib/x86_64-linux-gnu/libstdc++.so.6.0.21)
|   ->12.12% (20,025B) 0x4F00D22: std::string::reserve(unsigned long) (in /usr/lib/x86_64-linux-gnu/libstdc++.so.6.0.21)
|     ->12.12% (20,025B) 0x4F00FB1: std::string::append(char const*, unsigned long) (in /usr/lib/x86_64-linux-gnu/libstdc++.so.6.0.21)
|       ->12.07% (19,950B) 0x401285: run() (test.cpp:14)
|       | ->12.07% (19,950B) 0x403929: void std::_Bind_simple<void (*())()>::_M_invoke<>(std::_Index_tuple<>) (functional:1700)
|       |   ->12.07% (19,950B) 0x403864: std::_Bind_simple<void (*())()>::operator()() (functional:1688)
|       |     ->12.07% (19,950B) 0x4037D2: std::thread::_Impl<std::_Bind_simple<void (*())()> >::_M_run() (thread:115)
|       |       ->12.07% (19,950B) 0x4EE9C7E: ??? (in /usr/lib/x86_64-linux-gnu/libstdc++.so.6.0.21)
|       |         ->12.07% (19,950B) 0x53D06B8: start_thread (pthread_create.c:333)
|       |           ->12.07% (19,950B) 0x56ED3DB: clone (clone.S:109)
|       |             
|       ->00.05% (75B) in 1+ places, all below ms_print's threshold (01.00%)
|       
->05.58% (9,216B) 0x40315B: __gnu_cxx::new_allocator<std::_Sp_counted_ptr_inplace<std::thread::_Impl<std::_Bind_simple<void (*())()> >, std::allocator<std::thread::_Impl<std::_Bind_simple<void (*())()> > >, (__gnu_cxx::_Lock_policy)2> >::allocate(unsigned long, void const*) (new_allocator.h:104)
| ->05.58% (9,216B) 0x402FC2: std::allocator_traits<std::allocator<std::_Sp_counted_ptr_inplace<std::thread::_Impl<std::_Bind_simple<void (*())()> >, std::allocator<std::thread::_Impl<std::_Bind_simple<void (*())()> > >, (__gnu_cxx::_Lock_policy)2> > >::allocate(std::allocator<std::_Sp_counted_ptr_inplace<std::thread::_Impl<std::_Bind_simple<void (*())()> >, std::allocator<std::thread::_Impl<std::_Bind_simple<void (*())()> > >, (__gnu_cxx::_Lock_policy)2> >&, unsigned long) (alloc_traits.h:488)
|   ->05.58% (9,216B) 0x402D4B: std::__shared_count<(__gnu_cxx::_Lock_policy)2>::__shared_count<std::thread::_Impl<std::_Bind_simple<void (*())()> >, std::allocator<std::thread::_Impl<std::_Bind_simple<void (*())()> > >, std::_Bind_simple<void (*())()> >(std::_Sp_make_shared_tag, std::thread::_Impl<std::_Bind_simple<void (*())()> >*, std::allocator<std::thread::_Impl<std::_Bind_simple<void (*())()> > > const&, std::_Bind_simple<void (*())()>&&) (shared_ptr_base.h:616)
|     ->05.58% (9,216B) 0x402BDE: std::__shared_ptr<std::thread::_Impl<std::_Bind_simple<void (*())()> >, (__gnu_cxx::_Lock_policy)2>::__shared_ptr<std::allocator<std::thread::_Impl<std::_Bind_simple<void (*())()> > >, std::_Bind_simple<void (*())()> >(std::_Sp_make_shared_tag, std::allocator<std::thread::_Impl<std::_Bind_simple<void (*())()> > > const&, std::_Bind_simple<void (*())()>&&) (shared_ptr_base.h:1090)
|       ->05.58% (9,216B) 0x402A76: std::shared_ptr<std::thread::_Impl<std::_Bind_simple<void (*())()> > >::shared_ptr<std::allocator<std::thread::_Impl<std::_Bind_simple<void (*())()> > >, std::_Bind_simple<void (*())()> >(std::_Sp_make_shared_tag, std::allocator<std::thread::_Impl<std::_Bind_simple<void (*())()> > > const&, std::_Bind_simple<void (*())()>&&) (shared_ptr.h:316)
|         ->05.58% (9,216B) 0x402771: std::shared_ptr<std::thread::_Impl<std::_Bind_simple<void (*())()> > > std::allocate_shared<std::thread::_Impl<std::_Bind_simple<void (*())()> >, std::allocator<std::thread::_Impl<std::_Bind_simple<void (*())()> > >, std::_Bind_simple<void (*())()> >(std::allocator<std::thread::_Impl<std::_Bind_simple<void (*())()> > > const&, std::_Bind_simple<void (*())()>&&) (shared_ptr.h:594)
|           ->05.58% (9,216B) 0x402325: std::shared_ptr<std::thread::_Impl<std::_Bind_simple<void (*())()> > > std::make_shared<std::thread::_Impl<std::_Bind_simple<void (*())()> >, std::_Bind_simple<void (*())()> >(std::_Bind_simple<void (*())()>&&) (shared_ptr.h:610)
|             ->05.58% (9,216B) 0x401F9C: std::shared_ptr<std::thread::_Impl<std::_Bind_simple<void (*())()> > > std::thread::_M_make_routine<std::_Bind_simple<void (*())()> >(std::_Bind_simple<void (*())()>&&) (thread:196)
|               ->05.58% (9,216B) 0x401BC4: std::thread::thread<void (*)()>(void (*&&)()) (thread:138)
|                 ->05.58% (9,216B) 0x401353: main (test.cpp:24)
|                   
->01.24% (2,048B) 0x402C9A: __gnu_cxx::new_allocator<std::thread>::allocate(unsigned long, void const*) (new_allocator.h:104)
  ->01.24% (2,048B) 0x402AF5: std::allocator_traits<std::allocator<std::thread> >::allocate(std::allocator<std::thread>&, unsigned long) (alloc_traits.h:488)
    ->01.24% (2,048B) 0x402928: std::_Vector_base<std::thread, std::allocator<std::thread> >::_M_allocate(unsigned long) (stl_vector.h:170)
      ->01.24% (2,048B) 0x40244E: void std::vector<std::thread, std::allocator<std::thread> >::_M_emplace_back_aux<std::thread>(std::thread&&) (vector.tcc:412)
        ->01.24% (2,048B) 0x40206D: void std::vector<std::thread, std::allocator<std::thread> >::emplace_back<std::thread>(std::thread&&) (vector.tcc:101)
          ->01.24% (2,048B) 0x401C82: std::vector<std::thread, std::allocator<std::thread> >::push_back(std::thread&&) (stl_vector.h:932)
            ->01.24% (2,048B) 0x401366: main (test.cpp:24)

Пожалуйста, игнорируйте дерьмовую обработку потоков, это всего лишь очень короткий пример.


ОБНОВЛЕНИЕ

Похоже, это вообще не связано с std::string.Как предположил @Lawrence, это можно воспроизвести, просто выделив одну кучу int в куче (с new).Я полагаю, что @Lawrence очень близок к реальному ответу здесь, цитируя его комментарии (легче для дальнейших читателей):

Лоуренс:

@ KirilKirov Распределение строк фактически не принимает этомного места ... Каждый поток получает свой начальный стек, а затем доступ к куче отображает некоторый большой объем пространства (около 70 м), который отражается неточно.Вы можете измерить его, просто объявив 1 строку и затем выполнив цикл вращения ... показано то же использование виртуальной памяти - Лоуренс, 28 сентября, 14: 51

me:

@ Лоуренс - ты чертовски прав!Итак, вы говорите (и похоже, что так), что в каждом потоке, при первом выделении кучи, менеджер памяти (или ОС, или что-то еще) выделяет огромный кусок памяти для кучи потоковпотребности?И этот кусок будет повторно использован позже (или уменьшен, если необходимо)?- Кирил Киров 28 сентября в 15: 45

Лоуренс:

@ КирилКиров что-то в этом роде ... точные распределения, вероятно, зависят от реализации malloc и еще много чего - Лоуренс 2дней назад

Ответы [ 4 ]

0 голосов
/ 07 октября 2018

Я постараюсь написать краткое резюме того, что я узнал, пытаясь выяснить, что происходит.
Примечание: этот ответ возможен благодаря @Lawrence - признателен!


Короче говоря

Это не имеет абсолютно никакого отношения ни к управлению памятью в Linux / ядре (виртуальной), ни к std::string.
Все дело в памяти glibcallocator - он просто выделяет огромные области памяти при первом (и не только, конечно) динамическом выделении (на поток) .


Подробности

MCVE

#include <thread>
#include <vector>
#include <chrono>

int main() {
    std::vector<std::thread> workers;
    for( unsigned i = 0; i < 192; ++i )
        workers.emplace_back([]{
            const auto x = std::make_unique<int>(rand());
            while (true) std::this_thread::sleep_for(std::chrono::seconds(1));});
    workers.back().join();
}

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

Команды

Компиляция: g++ --std=c++14 -fno-inline -g3 -O0 -pthread test.cpp.
Профиль: valgrind --tool=massif --pages-as-heap=[no|yes] ./a.out

Использование памяти

top показывает 7'815'012 КиБ виртуальной памяти.
pmap также показывает 7'815'016 КиБ виртуальной памяти.
Аналогичный результат показан massif с pages-as-heap=yes: 7'817'088 КиБ, см. Ниже.
С другой стороны, massif с pages-as-heap=no резко отличается - около 133 КиБ!

Вывод массива с pages-as-heap = yes

Использование памяти перед уничтожением программы:

100.00% (8,004,698,112B) (page allocation syscalls) mmap/mremap/brk, --alloc-fns, etc.
->99.78% (7,986,741,248B) 0x54E0679: mmap (mmap.c:34)
| ->46.11% (3,690,987,520B) 0x545C3CF: new_heap (arena.c:438)
| | ->46.11% (3,690,987,520B) 0x545CC1F: arena_get2.part.3 (arena.c:646)
| |   ->46.11% (3,690,987,520B) 0x5463248: malloc (malloc.c:2911)
| |     ->46.11% (3,690,987,520B) 0x4CB7E76: operator new(unsigned long) (in /usr/lib/x86_64-linux-gnu/libstdc++.so.6.0.21)
| |       ->46.11% (3,690,987,520B) 0x4026D0: std::_MakeUniq<int>::__single_object std::make_unique<int, int>(int&&) (unique_ptr.h:765)
| |         ->46.11% (3,690,987,520B) 0x400EC5: main::{lambda()
| |           ->46.11% (3,690,987,520B) 0x40225C: void std::_Bind_simple<main::{lambda()
| |             ->46.11% (3,690,987,520B) 0x402194: std::_Bind_simple<main::{lambda()
| |               ->46.11% (3,690,987,520B) 0x402102: std::thread::_Impl<std::_Bind_simple<main::{lambda()
| |                 ->46.11% (3,690,987,520B) 0x4CE2C7E: ??? (in /usr/lib/x86_64-linux-gnu/libstdc++.so.6.0.21)
| |                   ->46.11% (3,690,987,520B) 0x51C96B8: start_thread (pthread_create.c:333)
| |                     ->46.11% (3,690,987,520B) 0x54E63DB: clone (clone.S:109)
| |                       
| ->33.53% (2,684,354,560B) 0x545C35B: new_heap (arena.c:427)
| | ->33.53% (2,684,354,560B) 0x545CC1F: arena_get2.part.3 (arena.c:646)
| |   ->33.53% (2,684,354,560B) 0x5463248: malloc (malloc.c:2911)
| |     ->33.53% (2,684,354,560B) 0x4CB7E76: operator new(unsigned long) (in /usr/lib/x86_64-linux-gnu/libstdc++.so.6.0.21)
| |       ->33.53% (2,684,354,560B) 0x4026D0: std::_MakeUniq<int>::__single_object std::make_unique<int, int>(int&&) (unique_ptr.h:765)
| |         ->33.53% (2,684,354,560B) 0x400EC5: main::{lambda()
| |           ->33.53% (2,684,354,560B) 0x40225C: void std::_Bind_simple<main::{lambda()
| |             ->33.53% (2,684,354,560B) 0x402194: std::_Bind_simple<main::{lambda()
| |               ->33.53% (2,684,354,560B) 0x402102: std::thread::_Impl<std::_Bind_simple<main::{lambda()
| |                 ->33.53% (2,684,354,560B) 0x4CE2C7E: ??? (in /usr/lib/x86_64-linux-gnu/libstdc++.so.6.0.21)
| |                   ->33.53% (2,684,354,560B) 0x51C96B8: start_thread (pthread_create.c:333)
| |                     ->33.53% (2,684,354,560B) 0x54E63DB: clone (clone.S:109)
| |                       
| ->20.13% (1,611,399,168B) 0x51CA1D4: pthread_create@@GLIBC_2.2.5 (allocatestack.c:513)
|   ->20.13% (1,611,399,168B) 0x4CE2DC1: std::thread::_M_start_thread(std::shared_ptr<std::thread::_Impl_base>, void (*)()) (in /usr/lib/x86_64-linux-gnu/libstdc++.so.6.0.21)
|     ->20.13% (1,611,399,168B) 0x4CE2ECB: std::thread::_M_start_thread(std::shared_ptr<std::thread::_Impl_base>) (in /usr/lib/x86_64-linux-gnu/libstdc++.so.6.0.21)
|       ->20.13% (1,611,399,168B) 0x40139A: std::thread::thread<main::{lambda()
|         ->20.13% (1,611,399,168B) 0x4012AE: _ZN9__gnu_cxx13new_allocatorISt6threadE9constructIS1_IZ4mainEUlvE_EEEvPT_DpOT0_ (new_allocator.h:120)
|           ->20.13% (1,611,399,168B) 0x401075: _ZNSt16allocator_traitsISaISt6threadEE9constructIS0_IZ4mainEUlvE_EEEvRS1_PT_DpOT0_ (alloc_traits.h:527)
|             ->19.19% (1,535,864,832B) 0x401009: void std::vector<std::thread, std::allocator<std::thread> >::emplace_back<main::{lambda()
|             | ->19.19% (1,535,864,832B) 0x400F47: main (test.cpp:10)
|             |   
|             ->00.94% (75,534,336B) in 1+ places, all below ms_print's threshold (01.00%)
|             
->00.22% (17,956,864B) in 1+ places, all below ms_print's threshold (01.00%)

Вывод массива с pages-as-heap = no

Использование памяти перед уничтожением программы:

--------------------------------------------------------------------------------
  n        time(i)         total(B)   useful-heap(B) extra-heap(B)    stacks(B)
--------------------------------------------------------------------------------
 68      2,793,125          143,280          136,676         6,604            0
95.39% (136,676B) (heap allocation functions) malloc/new/new[], --alloc-fns, etc.
->50.74% (72,704B) 0x4EBAEFE: ??? (in /usr/lib/x86_64-linux-gnu/libstdc++.so.6.0.21)
| ->50.74% (72,704B) 0x40106B8: call_init.part.0 (dl-init.c:72)
|   ->50.74% (72,704B) 0x40107C9: _dl_init (dl-init.c:30)
|     ->50.74% (72,704B) 0x4000C68: ??? (in /lib/x86_64-linux-gnu/ld-2.23.so)
|       
->36.58% (52,416B) 0x40138A3: _dl_allocate_tls (dl-tls.c:322)
| ->36.58% (52,416B) 0x53D126D: pthread_create@@GLIBC_2.2.5 (allocatestack.c:588)
|   ->36.58% (52,416B) 0x4EE9DC1: std::thread::_M_start_thread(std::shared_ptr<std::thread::_Impl_base>, void (*)()) (in /usr/lib/x86_64-linux-gnu/libstdc++.so.6.0.21)
|     ->36.58% (52,416B) 0x4EE9ECB: std::thread::_M_start_thread(std::shared_ptr<std::thread::_Impl_base>) (in /usr/lib/x86_64-linux-gnu/libstdc++.so.6.0.21)
|       ->36.58% (52,416B) 0x40139A: std::thread::thread<main::{lambda()
|         ->36.58% (52,416B) 0x4012AE: _ZN9__gnu_cxx13new_allocatorISt6threadE9constructIS1_IZ4mainEUlvE_EEEvPT_DpOT0_ (new_allocator.h:120)
|           ->36.58% (52,416B) 0x401075: _ZNSt16allocator_traitsISaISt6threadEE9constructIS0_IZ4mainEUlvE_EEEvRS1_PT_DpOT0_ (alloc_traits.h:527)
|             ->34.77% (49,824B) 0x401009: void std::vector<std::thread, std::allocator<std::thread> >::emplace_back<main::{lambda()
|             | ->34.77% (49,824B) 0x400F47: main (test.cpp:10)
|             |   
|             ->01.81% (2,592B) 0x4010FF: void std::vector<std::thread, std::allocator<std::thread> >::_M_emplace_back_aux<main::{lambda()
|               ->01.81% (2,592B) 0x40103D: void std::vector<std::thread, std::allocator<std::thread> >::emplace_back<main::{lambda()
|                 ->01.81% (2,592B) 0x400F47: main (test.cpp:10)
|                   
->06.13% (8,784B) 0x401B4B: __gnu_cxx::new_allocator<std::_Sp_counted_ptr_inplace<std::thread::_Impl<std::_Bind_simple<main::{lambda()
| ->06.13% (8,784B) 0x401A60: std::allocator_traits<std::allocator<std::_Sp_counted_ptr_inplace<std::thread::_Impl<std::_Bind_simple<main::{lambda()
|   ->06.13% (8,784B) 0x40194D: std::__shared_count<(__gnu_cxx::_Lock_policy)2>::__shared_count<std::thread::_Impl<std::_Bind_simple<main::{lambda()
|     ->06.13% (8,784B) 0x401894: std::__shared_ptr<std::thread::_Impl<std::_Bind_simple<main::{lambda()
|       ->06.13% (8,784B) 0x40183A: std::shared_ptr<std::thread::_Impl<std::_Bind_simple<main::{lambda()
|         ->06.13% (8,784B) 0x4017C7: std::shared_ptr<std::thread::_Impl<std::_Bind_simple<main::{lambda()
|           ->06.13% (8,784B) 0x4016AB: std::shared_ptr<std::thread::_Impl<std::_Bind_simple<main::{lambda()
|             ->06.13% (8,784B) 0x40155E: std::shared_ptr<std::thread::_Impl<std::_Bind_simple<main::{lambda()
|               ->06.13% (8,784B) 0x401374: std::thread::thread<main::{lambda()
|                 ->06.13% (8,784B) 0x4012AE: _ZN9__gnu_cxx13new_allocatorISt6threadE9constructIS1_IZ4mainEUlvE_EEEvPT_DpOT0_ (new_allocator.h:120)
|                   ->06.13% (8,784B) 0x401075: _ZNSt16allocator_traitsISaISt6threadEE9constructIS0_IZ4mainEUlvE_EEEvRS1_PT_DpOT0_ (alloc_traits.h:527)
|                     ->05.83% (8,352B) 0x401009: void std::vector<std::thread, std::allocator<std::thread> >::emplace_back<main::{lambda()
|                     | ->05.83% (8,352B) 0x400F47: main (test.cpp:10)
|                     |   
|                     ->00.30% (432B) in 1+ places, all below ms_print's threshold (01.00%)
|                     
->01.43% (2,048B) 0x403432: __gnu_cxx::new_allocator<std::thread>::allocate(unsigned long, void const*) (new_allocator.h:104)
| ->01.43% (2,048B) 0x4032CF: std::allocator_traits<std::allocator<std::thread> >::allocate(std::allocator<std::thread>&, unsigned long) (alloc_traits.h:488)
|   ->01.43% (2,048B) 0x4030B8: std::_Vector_base<std::thread, std::allocator<std::thread> >::_M_allocate(unsigned long) (stl_vector.h:170)
|     ->01.43% (2,048B) 0x4010B6: void std::vector<std::thread, std::allocator<std::thread> >::_M_emplace_back_aux<main::{lambda()
|       ->01.43% (2,048B) 0x40103D: void std::vector<std::thread, std::allocator<std::thread> >::emplace_back<main::{lambda()
|         ->01.43% (2,048B) 0x400F47: main (test.cpp:10)
|           
->00.51% (724B) in 1+ places, all below ms_print's threshold (01.00%)

Что за урод происходит?

pages-as-heap = no

С pages-as-heap=no все выглядит разумно - давайте не будем это проверять.Как и ожидалось, все заканчивается malloc/new/new[], а использование памяти достаточно мало, чтобы не беспокоить нас - это высокоуровневые выделения.

pages-as-heap = yes

Но посмотритеpages-as-heap=yes?~ 8 ГБ виртуальной памяти с этим простым кодом?

Давайте проверим следы стека.

pthread_create

Давайте начнем с более простого: того, который заканчивается pthread_create.

massif отчетов 1,611,399,168 байт выделенной памяти - это ровно 192 * 8'196 КиБ, что означает - 192 потока * 8MiB, , что является максимальным размером стека потока по умолчанию в Linux .

Обратите внимание , что 8'196 КиБ не совсем 8 МБ (8'192 КиБ).Я не знаю, откуда взялась эта разница, но на данный момент она незначительна.

std::make_unique<int>

Хорошо, давайте теперь посмотрим на другие два стека... подожди, они точно такие же?Да, документация massif объясняет это, я не совсем понял это, но это также не важно.Они показывают точно такой же стек.Давайте объединим результаты и рассмотрим их вместе.

Использование памяти из этих двух стеков вместе составляет 6'375'342'080 байтов, и все они вызваны нашим простым std::make_unique<int>!

Давайте возьмемшаг назад: если мы выполним тот же эксперимент, но с простым потоком, мы увидим, что это int выделение приводит к выделению 67'108'864 байтов памяти, что в точности равно 64 МБ.Что происходит ??

Все сводится к реализации malloc (как мы все знаем, что new/new[] внутренне реализован с malloc .. по умолчанию).

malloc внутренне использует распределитель памяти, называемый ptmalloc2 - распределитель памяти по умолчанию в Linux, который поддерживает потоки.

Проще говоря, этот распределитель имеет дело со следующими терминами:

  • per thread arena: огромная область памяти;обычно на поток, по соображениям производительности;не все программные потоки имеют свои арены для потоков , обычно это зависит от количества аппаратных потоков (и других деталей, я полагаю);
  • heap: arena s разделены на кучи;
  • chunks: heap s разделены на меньшие области памяти, называемые chunks.

Есть много деталейОб этих вещах мы опубликуем несколько интересных ссылок чуть позже, хотя этого должно быть достаточно для того, чтобы читатель мог провести собственное исследование - это действительно низкоуровневые и глубокие вещи, связанные с управлением памятью в C ++.

Итак, давайте вернемся к нашему тесту с одним потоком - выделил 64 МБ для одного int ??Давайте снова посмотрим на трассировку стека и сконцентрируемся в конце:

mmap (mmap.c:34)
new_heap (arena.c:438)
arena_get2.part.3 (arena.c:646)
malloc (malloc.c:2911)

Сюрприз, сюрприз: malloc вызывает arena_get2, что вызывает new_heap, что приводит нас к mmap (mmapи brk - системные вызовы низкого уровня, используемые для выделения памяти в Linux).И это, как сообщается, выделяет ровно 64 МБ памяти.

Хорошо, теперь давайте вернемся к нашему первоначальному примеру с 192 потоками и нашим огромным числом 6'375'342'080 - это точно 95 *64 МиБ!

Почему именно 95 - я не могу сказать, я перестал копать, но тот факт, что большое число делится на 64 МиБ, был достаточно хорош для меня.

Вы можете копать глубже, если это необходимо.

Полезные ссылки

Действительно классная пояснительная статья: Понимание glibc malloc , от sploitfun

Более официальная / официальная документация: Распределитель GNU

Вопрос об обмене классным стеком: Как работает glibc malloc

Другие:

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

Спасибо

Я надеюсь, что эти наблюдения дают хорошее общее описание всей картины, а также дают достаточно пищи для дальнейших расширенных исследований.

Не стесняйтесь комментировать / (предложить) изменить / исправить / расширить / и т. д.

0 голосов
/ 01 октября 2018

massif с --pages-as-heap=yes и столбцом top, который вы наблюдаете, измеряют виртуальную память, используемую процессом.Эта виртуальная память включает в себя все пространство mmap 'd при реализации malloc и при создании потоков.Например, размер стека по умолчанию для потока будет 8192k, что отражается при создании каждого потока и вносит вклад в объем виртуальной памяти.

Конкретная схема распределения будет зависеть от реализации, но, похоже,что первое выделение кучи в новом потоке будет mmap примерно 65 мегабайт пространства.Это можно увидеть, посмотрев вывод процесса pmap .

Выдержка из программы, очень похожей на пример:

75170:   ./a.out
0000000000400000     24K r-x-- a.out
0000000000605000      4K r---- a.out
0000000000606000      4K rw--- a.out
0000000001b6a000    200K rw---   [ anon ]
00007f669dfa4000      4K -----   [ anon ]
00007f669dfa5000   8192K rw---   [ anon ]
00007f669e7a5000      4K -----   [ anon ]
00007f669e7a6000   8192K rw---   [ anon ]
00007f669efa6000      4K -----   [ anon ]
00007f669efa7000   8192K rw---   [ anon ]
...
00007f66cb800000   8192K rw---   [ anon ]
00007f66cc000000    132K rw---   [ anon ]
00007f66cc021000  65404K -----   [ anon ]
00007f66d0000000    132K rw---   [ anon ]
00007f66d0021000  65404K -----   [ anon ]
00007f66d4000000    132K rw---   [ anon ]
00007f66d4021000  65404K -----   [ anon ]
...
00007f6880586000   8192K rw---   [ anon ]
00007f6880d86000   1056K r-x-- libm-2.23.so
00007f6880e8e000   2044K ----- libm-2.23.so
...
00007f6881c08000      4K r---- libpthread-2.23.so
00007f6881c09000      4K rw--- libpthread-2.23.so
00007f6881c0a000     16K rw---   [ anon ]
00007f6881c0e000    152K r-x-- ld-2.23.so
00007f6881e09000     24K rw---   [ anon ]
00007f6881e33000      4K r---- ld-2.23.so
00007f6881e34000      4K rw--- ld-2.23.so
00007f6881e35000      4K rw---   [ anon ]
00007ffe9d75b000    132K rw---   [ stack ]
00007ffe9d7f8000     12K r----   [ anon ]
00007ffe9d7fb000      8K r-x--   [ anon ]
ffffffffff600000      4K r-x--   [ anon ]
 total          7815008K

Кажется, что malloc становитсяболее консервативно, когда вы приближаетесь к некоторому порогу виртуальной памяти на процесс.Кроме того, мой комментарий о том, что библиотеки отображались отдельно, был ошибочным (они должны быть общими для каждого процесса)

0 голосов
/ 04 октября 2018

Посмотрите документацию:

- pages-as-heap = [default: no] Указывает массиву профилировать память на уровне страницы, а не на уровне блока malloc.Подробности см. Выше.

Таким образом, в соответствии с документацией, изменение этого параметра изменяет то, что измеряется;не то, что выделено.

ЕСЛИ ДА, вы измеряете количество страниц.Если НЕТ, вы измеряете блоки malloc.

0 голосов
/ 01 октября 2018

Это всего лишь «своего рода» ответ (с точки зрения Вальгринда).Проблема пулов памяти, в частности со строками C ++, известна уже давно.В руководстве Valgrind есть раздел об утечках в строках C ++, предлагающий попытаться установить переменную среды GLIBCXX_FORCE_NEW.

Кроме того, для GCC6 и более поздних версий Valgrind добавил хуки для очистки все еще доступной памятив libstdc ++.Запись в Valgrind bugzilla - здесь , а в GCC - здесь .

Я не понимаю, почему такие небольшие объемы выделяют столько гигабайт (более 12 Гбайт)для 64-битного исполняемого файла, CentOS 6.6, GCC 6.2).

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...