C++ std::vector ведет себя как утечка памяти в определенных ситуациях

Я нашел ситуацию, когда векторы ведут себя как утечка памяти и могут свести ее к минимальному рабочему примеру. В этом примере я делаю (в функции) вектор, который содержит три вектора char. Во-первых, эти векторы char выталкиваются большим количеством элементов, а их емкости сжимаются до их размеров. Затем над большими векторами назначаются векторы размером в один элемент. Проблема в том, что используемая память слишком велика и даже когда функция возвращает и векторы уничтожаются, память не освобождается. Как мне вернуть память? Почему это показывают это поведение? Что я могу сделать, чтобы избежать этой утечки?

вот пример кода (извините за длину):

#include <iostream>
#include <vector>
#include <string>
#include <fstream>

using namespace std;

// see http://man7.org/linux/man-pages/man5/proc.5.html at /proc/[pid]/status
string meminfo() {
    // memory information is in lines 11 - 20 of /proc/self/status
    ifstream stat_stream("/proc/self/status",ios_base::in);

    // get VmSize from line 12
    string s;
    for ( int linenum = 0; linenum < 12; ++linenum )
        getline(stat_stream,s);
    stat_stream.close();
    return s;
}

void f() {
    vector<vector<char>> mem(3); // with 1,2 memory is fine
    size_t size = 16777215;      // with 16777216 or greater memory is fine
    for ( vector<char>& v : mem ) {
        for ( unsigned int i = 0; i < size; ++i )
            v.push_back(i);
        v.shrink_to_fit();       // without this call memory is fine
    }

    cout << "Allocated vectors with capacities ";
    for ( vector<char>& v : mem )
        cout << v.capacity() << ", ";
    cout << endl << "used memory is now:   " << meminfo() << endl;

    for ( vector<char>& v : mem ) {
        v = vector<char>{1};
        if ( v.size() != v.capacity() )
            cout << "Capacity larger than size." << endl;
    }
    cout << "Shrinked vectors down to capacity 1." << endl
        << "Used memory is now:   " << meminfo() << endl;
}

int main() {
    cout << "At beginning of main: " << meminfo() << endl;
    f();
    cout << "At end of main:       " << meminfo() << endl;
    return 0;
}

и выход на моей машине:

At beginning of main: VmSize:      12516 kB
Allocated vectors with capacities 16777215, 16777215, 16777215,
used memory is now:   VmSize:      78060 kB
Shrinked vectors down to capacity 1.
Used memory is now:   VmSize:      61672 kB
At end of main:       VmSize:      61672 kB

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

Я думаю, что параметры в Примере зависят от системы, чтобы показать странный bahaviour. Я использую Linux Mint Debian Выпуск с ядром g++ 4.8.2 и x86_64. Я компилирую с:

g++ -std=c++11 -O0 -Wall memory.cpp -o memory

и попробовал также-O3 и никаких явных настроек для оптимизации.

некоторые интересные моменты:

  • когда я заменить v = vector<char>{1}; by v.clear(); v.shrink_to_fit(); v.push_back(1); проблема остается той же. Замена нажатия и сжатия для больших векторов на v = vector<char>(16777215); "решает" проблему памяти.
  • 16777215 = 2^24 - 1, так может, это что-то с памятью границы государств.
  • кроме того, можно было бы ожидать от памяти программы, используются в начале основной (12516 КБ) плюс память о больших векторов, которые будут использоваться в общей сложности примерно 3*16777216 Б + 12516 КБ = 61668 КБ, что примерно памяти он использует в конце.

в реальном приложении я использую векторы для сбора операций, которые должны применяться к матрице жесткости моделирования FEM. С тех пор, как я хочу дойти до предела. из того, что возможно с доступной памятью (также с точки зрения скорости), мне нужно сохранить несвободную память, чтобы избежать замены. Поскольку обмен действительно происходит, я предполагаю, что значение VmSize является надежным.

2 ответов


проблема в том, что вы неправильно понимаете значение "освобождения" памяти в контексте C++. Когда ваше приложение освобождает память (используя shrink_to_fit или удаление объектов или что-то еще), его на самом деле просто выпускает память в среду выполнения C++ и не обязательно выпускает ее обратно в систему для других процессов. Среда выполнения C++ может выбрать сохранение памяти для повторного использования позже в том же процессе.

обычно это происходит, когда память фрагментирована -- свободная память окружена (в пространстве виртуальной машины программы) встроенной памятью. Только когда освобожденная память находится в конце пространства памяти программы, среда выполнения C++ выберет (или сможет) вернуть ее в систему.

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


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

ОС должна присутствовать на запросе от всех процессов, запущенных в машине, поэтому не рекомендуется много раз запрашивать небольшие куски памяти. Лучше просить большие куски меньше раз. Менеджер памяти, работающий в вашем приложении, будет хранить некоторые куски памяти в надежде, что они понадобятся вашей заявке позже.