суббота, 21 июня 2014 г.

Шаблонное метапрограммирование в C++: генерация списков с произвольными полями

Я приведу псевдокод, который отразит смысл задачи.
list  Person1;

Person1.add_record( Name: "Paul" );
Person1.add_record( Age: 45 );
Список Person1 из этого примера можно реализовать с помощью структуры с полями Name и Age. Но в следующем определении мы, возможно, захотим изменить поля списка и нам понадобится новая структура. Как обойтись без явного определения структур-контейнеров и использовать одно обобщенное определение? Слово обобщенный (generic) содержит ответ — нужно привлечь к решению этой задачи шаблоны C++. Нам нужны линейные комбинации полей списков. Публичное наследование в C++ — отличный инструмент для создания линейных комбинаций! Я приведу код, добавляющий новую запись в список, а затем объясню его смысл. Имя файла — generator.hh. Здесь и далее стражи включения пропущены ради лаконичности.
#define DECLARE_RECORD( Name, Type, Value ) \
    template < typename  Parent >           \
    struct  Rec##Name : public Parent       \
    {                                       \
        Rec##Name() : Name( Value ) {}      \
        Type  Name;                         \
    };                                      \
    template <>                             \
    struct  Rec##Name< Generator::End >     \
    {                                       \
        Rec##Name() : Name( Value ) {}      \
        Type  Name;                         \
    };

namespace  Generator
{
    typedef int  End;
}
Макрос DECLARE_RECORD объявляет шаблоны структур с именем Rec##Name, которые содержат одну единственную запись, соответствующую одной из записей в формируемом списке: он реализует функцию add_record() из приведенного выше псевдокода. Структура может наследовать типу Parent — такой же структуре с другой единственной записью, либо замыкаться с помощью объявления Generator::End. Тип этого объявления может быть достаточно произвольным, я выбрал int. Формирование списка должно представлять собой создание списка типов из типов, объявленных с помощью DECLARE_RECORD. Давайте создадим простую базу персональных данных: файл persons.hh.
#include "generator.hh"

#define DECLARE_PERSON( Name, Id ) \
    Persons::Id::RecName< Persons::Id::RecAge< Generator::End > >  Name;

namespace  Persons
{
    namespace  Person1
    {
        DECLARE_RECORD( Name, const char *, "Paul" )
        DECLARE_RECORD( Age, size_t, 45 )
    }

    namespace  Person2
    {
        DECLARE_RECORD( Name, const char *, "Helen" )
        DECLARE_RECORD( Age, size_t, 38 )
    }
}
Макрос DECLARE_PERSON — это и есть механизм для формирования списка с записями имени (Name) и возраста (Age) персоналий. Список формируется нанизыванием имен структур, объявленных макросами DECLARE_RECORD ниже и заканчивается объявлением Generator::End. Для различения персоналий используются пространства имен Person1 и Person2. Заметьте, что здесь нет ни объектов времени исполнения, ни объектов времени компиляции: объявления DECLARE_RECORD разворачивают объявления шаблонов на стадии препроцессорной обработки, инстанцирование же этих шаблонов будет происходить во время применения макроса DECLARE_PERSON. Давайте напишем простой тест (файл test.cc).
#include <iostream>
#include "persons.hh"

int  main( void )
{
    DECLARE_PERSON( rec1, Person1 )
    std::cout << "1. Name: " << rec1.Name << ", Age: " << rec1.Age << std::endl;

    DECLARE_PERSON( rec2, Person2 )
    std::cout << "2. Name: " << rec2.Name << ", Age: " << rec2.Age << std::endl;
}
После компиляции запустим программу на исполнение.
g++ -o test test.cc
./test
1. Name: Paul, Age: 45
2. Name: Helen, Age: 38
Работает. А теперь создадим базу данных книг в файле books.hh. В каждой записи будет три поля: название (Name), автор (Author) и год выпуска (Year).
#include "generator.hh"

#define DECLARE_BOOK( Name, Id ) \
    Books::Id::RecName< Books::Id::RecAuthor< \
                                Books::Id::RecYear< Generator::End > > >  Name;

namespace  Books
{
    namespace  Book1
    {
        DECLARE_RECORD( Name, const char *, "War and Peace" )
        DECLARE_RECORD( Author, const char *, "Lev Tolstoy" )
        DECLARE_RECORD( Year, int, 1873 )
    }
}
Макрос DECLARE_BOOK выполняет ту же роль, что и DECLARE_PERSON при создании списка персональных данных, то есть создает список данных одной книги путем нанизывания типов, объявленных ниже с помощью макросов DECLARE_RECORD, в список типов, замыкаемый объявлением Generator::End. Добавим нашу новую книгу в тестовую программу. Для этого в файле test.cc нужно включить файл books.hh
#include "books.hh"
и в конце функции main() поместить строки
    DECLARE_BOOK( rec3, Book1 )
    std::cout << "3. Name: " << rec3.Name << ", Author: " << rec3.Author <<
                 ", Year: " << rec3.Year << std::endl;
Компилируем и запускаем на исполнение.
g++ -o test test.cc
./test
1. Name: Paul, Age: 45
2. Name: Helen, Age: 38
3. Name: War and Peace, Author: Lev Tolstoy, Year: 1873

вторник, 10 июня 2014 г.

malloc и значения VSZ и RSS в выводе программ для мониторинга процессов

Я приведу небольшой пример, в котором будет показано различие в природе этих значений и возможные способы управления ими (по крайней мере, значением VSZ). Как ни странно, несмотря на очевидное смысловое различие между виртуальной памятью процесса (VSZ) и резидентной (или физической) памятью процесса (RSS), находятся люди, которые судят об утечке памяти в программе на основании роста (а иногда неосвобождения) последней. Обычно программист может контролировать только виртуальную память (собственно, адресное пространство, доступное в программе относится к виртуальной памяти). Управлением физической памятью занимается ядро операционной системы, и ее рост и неосвобождение могут быть связаны с особенностями реализации в нем (ядре) управления страницами физической памяти. Соответственно, рост RSS может лишь косвенно свидетельствовать о возможной утечке виртуальной памяти, а ее неосвобождение так и вообще ни о чем таком не свидетельствует, поскольку ядро может возвращать в систему аллоцированные процессу страницы когда ему вздумается. Единственный относительно надежный способ судить об утечке памяти в программе — это следить за величиной VSZ. Почему относительно надежный: ну, во-первых, VSZ включает адресные пространства разделяемых библиотек, которые тоже могут, в принципе, течь, а во-вторых … И тут мы переходим к анонсированному примеру.
#include <unistd.h>
#include <iostream>

int  main( void )
{
    const size_t  ncycles( 100 );

    for ( int  i( ncycles ); i > 0; --i )
    {
        int *  a( new int[ 1024 * i ] );
        std::cout << "Cycle " << ncycles - i + 1 <<
                     ", addr: " << a << std::endl;
        usleep( 500000 );
        delete[] a;
    }

    return 0;
}
Простая программа на C++. В цикле выделяем большие куски памяти: сначала самые большие, затем все меньше и меньше. Внутри цикла устанавливаем задержку в пол-секунды для того, чтобы можно было удобно мониторить программу с помощью утилиты pidstat. Скомпилируем программу и дадим ей название malloc_test.
g++ -o malloc_test main.cc
Запустим программу и мониторинг в разных терминалах. Мы ожидаем, что VSZ будет постепенно уменьшаться, предположений о поведении RSS пока выдвигать не будем. А вот что показал pidstat на самом деле:
pidstat -r -p $(pgrep -x malloc_test) 2 30
Linux 3.14.5-200.fc20.x86_64 (localhost.localdomain)    10.06.2014  _x86_64_    (4 CPU)

18:53:44      UID       PID  minflt/s  majflt/s     VSZ    RSS   %MEM  Command
18:53:46     1000     20532      2,00      0,00   13032   1152   0,01  malloc_test
18:53:48     1000     20532      2,00      0,00   13032   1152   0,01  malloc_test
18:53:50     1000     20532      2,00      0,00   13032   1152   0,01  malloc_test
18:53:52     1000     20532      2,00      0,00   13032   1152   0,01  malloc_test
18:53:54     1000     20532      2,00      0,00   13032   1152   0,01  malloc_test
18:53:56     1000     20532      2,00      0,00   13032   1152   0,01  malloc_test
18:53:58     1000     20532      2,00      0,00   13032   1152   0,01  malloc_test
18:54:00     1000     20532      2,00      0,00   13032   1152   0,01  malloc_test
18:54:02     1000     20532      2,00      0,00   13032   1152   0,01  malloc_test
18:54:04     1000     20532      2,00      0,00   13032   1152   0,01  malloc_test
18:54:06     1000     20532      2,00      0,00   13032   1152   0,01  malloc_test
18:54:08     1000     20532      2,00      0,00   13032   1152   0,01  malloc_test
18:54:10     1000     20532      2,00      0,00   13032   1152   0,01  malloc_test
18:54:12     1000     20532      2,00      0,00   13032   1152   0,01  malloc_test
18:54:14     1000     20532      2,00      0,00   13032   1412   0,02  malloc_test
18:54:16     1000     20532      2,00      0,00   13032   1412   0,02  malloc_test
18:54:18     1000     20532      2,00      0,00   13032   1412   0,02  malloc_test
18:54:20     1000     20532      2,00      0,00   13032   1412   0,02  malloc_test
18:54:22     1000     20532      2,00      0,00   13032   1412   0,02  malloc_test
18:54:24     1000     20532      2,00      0,00   13032   1412   0,02  malloc_test
18:54:26     1000     20532      2,00      0,00   13032   1412   0,02  malloc_test
18:54:28     1000     20532      2,00      0,00   13032   1412   0,02  malloc_test
18:54:30     1000     20532      2,00      0,00   13032   1412   0,02  malloc_test
18:54:32     1000     20532      2,00      0,00   13032   1412   0,02  malloc_test
Значение VSZ не падает, а RSS вообще растет. О, ужас! Неужели утечка памяти? Но программа-то вроде простая, явных ошибок в ней нет. Открываем man mallopt и читаем о способах управления параметрами malloc. Комбинация M_TOP_PAD и M_TRIM_THRESHOLD выглядит заманчиво, листаем чуть ниже и узнаем, что их можно задать через переменные окружения MALLOC_TOP_PAD_ и MALLOC_TRIM_THRESHOLD_ без перекомпиляции программы! Зануляем эти значения и запускаем malloc_test снова.
MALLOC_TRIM_THRESHOLD_=0 MALLOC_TOP_PAD_=0 ./malloc_test
На этот раз вывод pidstat выглядит так:
19:07:18      UID       PID  minflt/s  majflt/s     VSZ    RSS   %MEM  Command
19:07:20     1000     32224      2,00      0,00   12876   1164   0,01  malloc_test
19:07:22     1000     32224      2,00      0,00   12860   1164   0,01  malloc_test
19:07:24     1000     32224      2,00      0,00   12844   1164   0,01  malloc_test
19:07:26     1000     32224      2,00      0,00   12828   1164   0,01  malloc_test
19:07:28     1000     32224      2,00      0,00   12812   1164   0,01  malloc_test
19:07:30     1000     32224      2,00      0,00   12796   1164   0,01  malloc_test
19:07:32     1000     32224      2,00      0,00   12780   1164   0,01  malloc_test
19:07:34     1000     32224      1,99      0,00   12764   1164   0,01  malloc_test
19:07:36     1000     32224      2,00      0,00   12748   1164   0,01  malloc_test
19:07:38     1000     32224      2,00      0,00   12732   1164   0,01  malloc_test
19:07:40     1000     32224      2,00      0,00   12716   1164   0,01  malloc_test
19:07:42     1000     32224      2,00      0,00   12700   1164   0,01  malloc_test
19:07:44     1000     32224      2,00      0,00   12684   1164   0,01  malloc_test
19:07:46     1000     32224      2,00      0,00   12668   1164   0,01  malloc_test
19:07:48     1000     32224      2,00      0,00   12652   1164   0,01  malloc_test
19:07:50     1000     32224      2,00      0,00   12636   1164   0,01  malloc_test
19:07:52     1000     32224      4,00      0,00   12620   1180   0,01  malloc_test
19:07:54     1000     32224      2,00      0,00   12604   1180   0,01  malloc_test
19:07:56     1000     32224      2,00      0,00   12588   1180   0,01  malloc_test
19:07:58     1000     32224      2,00      0,00   12572   1180   0,01  malloc_test
19:08:00     1000     32224      2,00      0,00   12564   1180   0,01  malloc_test
19:08:02     1000     32224      2,00      0,00   12564   1180   0,01  malloc_test
19:08:04     1000     32224      2,00      0,00   12564   1180   0,01  malloc_test
Круто, объем виртуальной памяти уменьшается! Но RSS по-прежнему увеличивается скачком под конец работы программы. Давайте посмотрим на вывод malloc_test:
Cycle 1, addr: 0x7fbbeda72010
Cycle 2, addr: 0x7fbbeda73010
Cycle 3, addr: 0x7fbbeda74010
Cycle 4, addr: 0x7fbbeda75010
Cycle 5, addr: 0x7fbbeda76010
Cycle 6, addr: 0x7fbbeda77010
    ...
Cycle 65, addr: 0x7fbbedaea010
Cycle 66, addr: 0x7fbbedaeb010
Cycle 67, addr: 0x7fbbedaec010
Cycle 68, addr: 0x7fbbedaed010
Cycle 69, addr: 0x7fbbedaee010
Cycle 70, addr: 0x10e3010
Cycle 71, addr: 0x10e3010
Cycle 72, addr: 0x10e3010
Cycle 73, addr: 0x10e3010
    ...
Cycle 96, addr: 0x10e3010
Cycle 97, addr: 0x10e3010
Cycle 98, addr: 0x10e3010
Cycle 99, addr: 0x10e3010
Cycle 100, addr: 0x10e3010
Заметили качественный скачок в значениях адресов выделенной памяти на 70-ом шаге, то есть когда размер выделяемой виртуальной памяти стал меньше примерно 1024 x 30 x sizeof(int), то есть 128 килобайт? Давайте заново запустим программу и проследим карту ее памяти до и после скачка. До:
cat /proc/$(pgrep -x malloc_test)/maps
00400000-00401000 r-xp 00000000 08:02 5907699                            /home/lyokha/tmp/28/malloc_test
00600000-00601000 r--p 00000000 08:02 5907699                            /home/lyokha/tmp/28/malloc_test
00601000-00602000 rw-p 00001000 08:02 5907699                            /home/lyokha/tmp/28/malloc_test
3748800000-3748820000 r-xp 00000000 08:01 353673                         /usr/lib64/ld-2.18.so
3748a1f000-3748a20000 r--p 0001f000 08:01 353673                         /usr/lib64/ld-2.18.so
3748a20000-3748a21000 rw-p 00020000 08:01 353673                         /usr/lib64/ld-2.18.so
3748a21000-3748a22000 rw-p 00000000 00:00 0 
3748c00000-3748db4000 r-xp 00000000 08:01 353674                         /usr/lib64/libc-2.18.so
3748db4000-3748fb4000 ---p 001b4000 08:01 353674                         /usr/lib64/libc-2.18.so
3748fb4000-3748fb8000 r--p 001b4000 08:01 353674                         /usr/lib64/libc-2.18.so
3748fb8000-3748fba000 rw-p 001b8000 08:01 353674                         /usr/lib64/libc-2.18.so
3748fba000-3748fbf000 rw-p 00000000 00:00 0 
3749800000-3749905000 r-xp 00000000 08:01 353677                         /usr/lib64/libm-2.18.so
3749905000-3749b05000 ---p 00105000 08:01 353677                         /usr/lib64/libm-2.18.so
3749b05000-3749b06000 r--p 00105000 08:01 353677                         /usr/lib64/libm-2.18.so
3749b06000-3749b07000 rw-p 00106000 08:01 353677                         /usr/lib64/libm-2.18.so
374a000000-374a015000 r-xp 00000000 08:01 353678                         /usr/lib64/libgcc_s-4.8.2-20131212.so.1
374a015000-374a214000 ---p 00015000 08:01 353678                         /usr/lib64/libgcc_s-4.8.2-20131212.so.1
374a214000-374a215000 r--p 00014000 08:01 353678                         /usr/lib64/libgcc_s-4.8.2-20131212.so.1
374a215000-374a216000 rw-p 00015000 08:01 353678                         /usr/lib64/libgcc_s-4.8.2-20131212.so.1
374d400000-374d4e9000 r-xp 00000000 08:01 353679                         /usr/lib64/libstdc++.so.6.0.19
374d4e9000-374d6e9000 ---p 000e9000 08:01 353679                         /usr/lib64/libstdc++.so.6.0.19
374d6e9000-374d6f1000 r--p 000e9000 08:01 353679                         /usr/lib64/libstdc++.so.6.0.19
374d6f1000-374d6f3000 rw-p 000f1000 08:01 353679                         /usr/lib64/libstdc++.so.6.0.19
374d6f3000-374d708000 rw-p 00000000 00:00 0 
7fad31adf000-7fad31b44000 rw-p 00000000 00:00 0 
7fad31b77000-7fad31b79000 rw-p 00000000 00:00 0 
7fff3eddb000-7fff3edfd000 rw-p 00000000 00:00 0                          [stack]
7fff3edfe000-7fff3ee00000 r-xp 00000000 00:00 0                          [vdso]
ffffffffff600000-ffffffffff601000 r-xp 00000000 00:00 0                  [vsyscall]
После:
00400000-00401000 r-xp 00000000 08:02 5907699                            /home/lyokha/tmp/28/malloc_test
00600000-00601000 r--p 00000000 08:02 5907699                            /home/lyokha/tmp/28/malloc_test
00601000-00602000 rw-p 00001000 08:02 5907699                            /home/lyokha/tmp/28/malloc_test
01ba4000-01bbd000 rw-p 00000000 00:00 0                                  [heap]
3748800000-3748820000 r-xp 00000000 08:01 353673                         /usr/lib64/ld-2.18.so
3748a1f000-3748a20000 r--p 0001f000 08:01 353673                         /usr/lib64/ld-2.18.so
3748a20000-3748a21000 rw-p 00020000 08:01 353673                         /usr/lib64/ld-2.18.so
3748a21000-3748a22000 rw-p 00000000 00:00 0 
3748c00000-3748db4000 r-xp 00000000 08:01 353674                         /usr/lib64/libc-2.18.so
3748db4000-3748fb4000 ---p 001b4000 08:01 353674                         /usr/lib64/libc-2.18.so
3748fb4000-3748fb8000 r--p 001b4000 08:01 353674                         /usr/lib64/libc-2.18.so
3748fb8000-3748fba000 rw-p 001b8000 08:01 353674                         /usr/lib64/libc-2.18.so
3748fba000-3748fbf000 rw-p 00000000 00:00 0 
3749800000-3749905000 r-xp 00000000 08:01 353677                         /usr/lib64/libm-2.18.so
3749905000-3749b05000 ---p 00105000 08:01 353677                         /usr/lib64/libm-2.18.so
3749b05000-3749b06000 r--p 00105000 08:01 353677                         /usr/lib64/libm-2.18.so
3749b06000-3749b07000 rw-p 00106000 08:01 353677                         /usr/lib64/libm-2.18.so
374a000000-374a015000 r-xp 00000000 08:01 353678                         /usr/lib64/libgcc_s-4.8.2-20131212.so.1
374a015000-374a214000 ---p 00015000 08:01 353678                         /usr/lib64/libgcc_s-4.8.2-20131212.so.1
374a214000-374a215000 r--p 00014000 08:01 353678                         /usr/lib64/libgcc_s-4.8.2-20131212.so.1
374a215000-374a216000 rw-p 00015000 08:01 353678                         /usr/lib64/libgcc_s-4.8.2-20131212.so.1
374d400000-374d4e9000 r-xp 00000000 08:01 353679                         /usr/lib64/libstdc++.so.6.0.19
374d4e9000-374d6e9000 ---p 000e9000 08:01 353679                         /usr/lib64/libstdc++.so.6.0.19
374d6e9000-374d6f1000 r--p 000e9000 08:01 353679                         /usr/lib64/libstdc++.so.6.0.19
374d6f1000-374d6f3000 rw-p 000f1000 08:01 353679                         /usr/lib64/libstdc++.so.6.0.19
374d6f3000-374d708000 rw-p 00000000 00:00 0 
7fad31b3f000-7fad31b44000 rw-p 00000000 00:00 0 
7fad31b77000-7fad31b79000 rw-p 00000000 00:00 0 
7fff3eddb000-7fff3edfd000 rw-p 00000000 00:00 0                          [stack]
7fff3edfe000-7fff3ee00000 r-xp 00000000 00:00 0                          [vdso]
ffffffffff600000-ffffffffff601000 r-xp 00000000 00:00 0                  [vsyscall]
Видите в чем разница? После скачка появилась именованная секция [heap], в которой находится диапазон адресов, соответствующий адресам, выделенным программой начиная с 70-ого шага итерации (в данных выводах диапазоны немного отличаются, поскольку я запустил программу второй раз). В то же время адреса, которые выделялись до 70-ого шага, находятся в одной (а может быть в обеих) из двух неименованных секций перед секцией [stack]. Совершенно очевидно, что ядру пришлось выделить дополнительную страницу физической памяти для создания секции [heap] начиная с 70-ого шага: этим и объясняется скачок RSS. Но почему же поменялась стратегия выделения памяти? Открываем man malloc и читаем:
Обычно, malloc() распределяет память из кучи и подгоняет размер кучи
соответствующим образом с помощью sbrk(2). Если распределяемый блок памяти
больше чем MMAP_THRESHOLD байт, то реализация malloc() в glibc распределяет
память с помощью mmap(2) в виде частного анонимного отображения. По умолчанию,
значение MMAP_THRESHOLD равно 128 КБ, но его можно изменить с помощью
mallopt(3). На распределения, выполняемые с помощью mmap(2), не влияет
ограничитель ресурса RLIMIT_DATA (смотрите getrlimit(2)).
Вот и объяснение, откуда взялось значение 128 килобайт. И, кстати, значением MMAP_THRESHOLD тоже можно управлять через переменную окружения MALLOC_MMAP_THRESHOLD_, а значит в данном случае мы сможем остановить скачок RSS. Проверим.
MALLOC_TRIM_THRESHOLD_=0 MALLOC_TOP_PAD_=0 MALLOC_MMAP_THRESHOLD_=0 ./malloc_test
Вывод pidstat:
19:40:13      UID       PID  minflt/s  majflt/s     VSZ    RSS   %MEM  Command
19:40:15     1000     28448      2,00      0,00   12876   1164   0,01  malloc_test
19:40:17     1000     28448      2,00      0,00   12860   1164   0,01  malloc_test
19:40:19     1000     28448      2,00      0,00   12844   1164   0,01  malloc_test
19:40:21     1000     28448      2,00      0,00   12828   1164   0,01  malloc_test
19:40:23     1000     28448      2,00      0,00   12812   1164   0,01  malloc_test
19:40:25     1000     28448      2,00      0,00   12796   1164   0,01  malloc_test
19:40:27     1000     28448      2,00      0,00   12780   1164   0,01  malloc_test
19:40:29     1000     28448      2,00      0,00   12764   1164   0,01  malloc_test
19:40:31     1000     28448      2,00      0,00   12748   1164   0,01  malloc_test
19:40:33     1000     28448      2,00      0,00   12732   1164   0,01  malloc_test
19:40:35     1000     28448      2,00      0,00   12716   1164   0,01  malloc_test
19:40:37     1000     28448      2,00      0,00   12700   1164   0,01  malloc_test
19:40:39     1000     28448      2,00      0,00   12684   1164   0,01  malloc_test
19:40:41     1000     28448      2,00      0,00   12668   1164   0,01  malloc_test
19:40:43     1000     28448      2,00      0,00   12652   1164   0,01  malloc_test
19:40:45     1000     28448      2,00      0,00   12636   1164   0,01  malloc_test
19:40:47     1000     28448      2,00      0,00   12620   1164   0,01  malloc_test
19:40:49     1000     28448      2,00      0,00   12604   1164   0,01  malloc_test
19:40:51     1000     28448      2,00      0,00   12588   1164   0,01  malloc_test
19:40:53     1000     28448      2,00      0,00   12572   1164   0,01  malloc_test
19:40:55     1000     28448      2,00      0,00   12556   1164   0,01  malloc_test
19:40:57     1000     28448      2,00      0,00   12540   1164   0,01  malloc_test
19:40:59     1000     28448      2,00      0,00   12524   1164   0,01  malloc_test
Работает! Объем RSS не увеличился, как мы и предположили. Этот последний пример хорошо демонстрирует природу RSS. Однако то, что нам удалось программно повлиять на динамику ее изменения — скорее исключение из правила, нежели правило. Программист не обязан контролировать величину RSS, поскольку ею управляет ядро операционной системы по своему усмотрению. Задача программиста — следить за изменением величины VSZ, однако и тут полно нюансов из-за вклада разделяемых библиотек и латентности при освобождении памяти в случае настройки malloc по умолчанию.