C ++ загружает 500 МБ данных с вектором и использует 5 ГБ ОЗУ - PullRequest
1 голос
/ 29 января 2020

Я пишу код для сортировки большого фрагмента строк (~ 2 ГБ) и использую метод, аналогичный сортировке по группам. У меня есть около 47000 векторов, и каждый из них будет иметь в среднем ~ 100 элементов (символ *). (Из-за ввода, некоторые из них могут иметь много элементов, а некоторые могут быть пустыми)

Мой код для получения ввода выглядит примерно так:

#include <iostream>
#include <string>
#include <vector>
#include <cstring>
#define digitize(a) ((a>47 && a<58)*(a-48)+(a>96 && a<123)*(a-87)) //base 36 convert
int main(){
 int n = 0;
    scanf("%d\n", &n);

    vector<char *> buckets[46657]; // 36 *36 *36 = 46656




    for (int i = 0; i < n; i++) {
        char *temp = static_cast<char *>(calloc(255, sizeof(char)));
        scanf("%s\n", temp);
        int d1 = temp[0];
        int d2 = temp[1];
        int d3 = temp[2];

        int index = digitize(d1) * 1296 + digitize(d2) * 36 + digitize(d3); // 1296 = 36*36

        buckets[index].push_back(temp);

    }
}

оцифровка на самом деле является основой 36 конвертер. (т.е. 0: 0, 1: 1 .... a: 10, b: 11, ..., z: 36) Потому что мои данные состоят из цифр и строчных букв.

Запустив этот код на набор данных объемом 500 МБ (который генерируется случайным образом), использование оперативной памяти для файла превышает 4 ГБ и составляет около 5 ГБ. (ОС: Windows7 64bit, IDE: Jetbrains CLion, компилятор: G ++)

Это нормально? Я не понимаю, почему он использует это огромное количество оперативной памяти только для получения данных. Я также проверил вывод, добавив еще один l oop, и они были правильными. Так что не существует бесконечного L oop или чего-то подобного. Код работает нормально, но использует огромное количество оперативной памяти.

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

1 Ответ

4 голосов
/ 29 января 2020

Что заставляет вас думать, что вы потребляете 5 ГБ?

Вы создаете массив из 46657 vector<char*>. Каждый вектор имеет в среднем 100 char*, указывая на вновь выделенную строку размером 255 байтов. Это как минимум sizeof(buckets)+46657*100*(sizeof(char*)+255) байтов. В зависимости от реализации, это может быть около 1,2 Гб. Векторы могут содержать некоторое пространство, зарезервированное для более быстрого роста. Но это принципиально не изменит наш порядок величины.

Все это огромно, возможно, больше, чем нужно, но это далеко от 5Gb, которые вы измеряете. Но что вы измерили в первую очередь?

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

Чтобы узнать, сколько памяти занимает ваш код, вам нужно более точно отслеживать / профилировать память. Например, вы можете использовать valgrind или другие инструменты в зависимости от вашей ОС.

Предотвращение утечек

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

Таким образом, более безопасный подход может быть следующим:

vector<string> buckets[46657]; 
...
for ...
    string temp; // let the string take care of its own memory
    getline(cin, temp);  
    ...

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

...