Требования к памяти: куча против стека в переполнении стека

Итак, у меня был странный опыт этим вечером.

Я работал над программой на C ++, которая требовала некоторого способа чтения длинного списка простых объектов данных из файла и сохранения их в основной памяти, приблизительно 400 000 записей. Сам объект выглядит примерно так:

class Entry
{
public:
Entry(int x, int y, int type);
Entry(); ~Entry();
// some other basic functions
private:
int m_X, m_Y;
int m_Type;
};

Просто, правда? Ну, так как мне нужно было прочитать их из файла, у меня был какой-то цикл, как

Entry** globalEntries;
globalEntries = new Entry*[totalEntries];
entries = new Entry[totalEntries];// totalEntries read from file, about 400,000
for (int i=0;i<totalEntries;i++)
{
globalEntries[i] = new Entry(.......);
}

Это дополнение к программе добавило в программу около 25–35 мегабайт, когда я отслеживал ее в диспетчере задач. Простое изменение в распределении стека:

Entry* globalEntries;
globalEntries = new Entry[totalEntries];
for (int i=0;i<totalEntries;i++)
{
globalEntries[i] = Entry(.......);
}

и вдруг потребовалось всего 3 мегабайта. Почему это происходит? Я знаю, что у объектов-указателей есть немного дополнительной служебной информации (4 байта для адреса-указателя), но этого не должно быть достаточно, чтобы ЭТО сильно изменило ситуацию. Может ли это быть из-за того, что программа распределяет память неэффективно, и в результате между выделенной памятью появляются фрагменты нераспределенной памяти?

-1

Решение

Прежде всего, без указания, какой именно столбец вы смотрели, число в диспетчере задач ничего не значит. На современной операционной системе трудно даже определять что вы имеете в виду под «использованной памятью» — мы говорим о личных страницах? Рабочий набор? Только то, что остается в оперативной памяти? резервируется, но не фиксируется память? Кто платит за память, разделяемую между процессами? Включен ли отображенный в память файл?

Если вы просматриваете какую-то значимую метрику, невозможно увидеть 3 МБ используемой памяти — ваш объект имеет как минимум 12 байтов (при условии 32-битных целых чисел и без заполнения), поэтому для 400000 элементов потребуется около 4,58 МБ. Кроме того, я был бы удивлен, если бы это работало с распределением стека — размер стека по умолчанию в VC ++ составляет 1 МБ, у вас уже должно было быть переполнение стека.

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

  • стек (в основном) выделяется с самого начала, так что это память, которую вы номинально потребляете, даже не используя ее ни для чего (на самом деле виртуальная память и автоматическое расширение стека делают это немного сложнее, но это «достаточно верно»);
  • куча CRT непрозрачна для диспетчера задач: все, что он видит, — это память, выделяемая операционной системой для процесса, а не то, что куча C «реально» использует; куча увеличивается (запрашивая память у ОС) более чем строго необходимо, чтобы быть готовым к дальнейшим запросам памяти — так что вы видите, сколько памяти она готова отдать без дальнейших системных вызовов;
  • Ваш метод «отдельных выделений» имеет значительные накладные расходы. Всепрерывный массив, с которым вы получите new Entry[size] расходы size*sizeof(Entry) байты плюс данные учета кучи (обычно это несколько полей целого размера); метод разделенных ассигнований стоит как минимум size*sizeof(Entry) (размер всех «голых элементов») плюс size*sizeof(Entry *) (размер массива указателей) плюс size+1 умножается на стоимость каждого размещения. Если мы примем 32-битную архитектуру со стоимостью 2 дюйма на выделение, вы быстро увидите, что это стоит size*24+8 байт памяти вместо size*12+8 для смежного массива в куче;
  • обычно куча отдает блоки, размер которых не соответствует запрашиваемому вами размеру, поскольку она управляет блоками фиксированного размера; таким образом, если вы выделяете отдельные объекты, подобные этим, вы, вероятно, платите также за дополнительное заполнение — предположим, что у него 16 блоков байтов, вы платите дополнительно 4 байта за элемент, выделяя их отдельно; это перемещает оценку памяти в size*28+8то есть накладные расходы 16 байтов на каждый 12-байтовый элемент.
1

Другие решения

Ваш код неверен, или я не вижу, как это работает. С new Entry [count] вы создаете новый массив Entry (тип Entry*), но вы назначаете его Entry**Я полагаю, что вы использовали new Entry*[count],

Затем вы создали еще один новый объект Entry в куче и сохранили его в globalEntries массив. Таким образом, вам нужна память для 400 000 указателей + 400 000 элементов. 400 000 указателей занимают 3 МБ памяти на 64-разрядной машине. Кроме того, у вас есть 400.000 однократных распределений, для чего потребуется sizeof (Entry) плюс, возможно, немного больше памяти (для менеджера памяти — может потребоваться сохранить размер выделения, связанный пул, выравнивание / заполнение и т. д.). Эта дополнительная память для учета может быстро сложиться.

Если вы измените свой второй пример на:

 Entry* globalEntries;
globalEntries = new Entry[count];
for (...) {
globalEntries [i] = Entry (...);
}

использование памяти должно соответствовать стековому подходу.

Конечно, в идеале вы будете использовать std::vector<Entry>,

2

По вопросам рекламы [email protected]