Итак, у меня был странный опыт этим вечером.
Я работал над программой на 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 байта для адреса-указателя), но этого не должно быть достаточно, чтобы ЭТО сильно изменило ситуацию. Может ли это быть из-за того, что программа распределяет память неэффективно, и в результате между выделенной памятью появляются фрагменты нераспределенной памяти?
Прежде всего, без указания, какой именно столбец вы смотрели, число в диспетчере задач ничего не значит. На современной операционной системе трудно даже определять что вы имеете в виду под «использованной памятью» — мы говорим о личных страницах? Рабочий набор? Только то, что остается в оперативной памяти? резервируется, но не фиксируется память? Кто платит за память, разделяемую между процессами? Включен ли отображенный в память файл?
Если вы просматриваете какую-то значимую метрику, невозможно увидеть 3 МБ используемой памяти — ваш объект имеет как минимум 12 байтов (при условии 32-битных целых чисел и без заполнения), поэтому для 400000 элементов потребуется около 4,58 МБ. Кроме того, я был бы удивлен, если бы это работало с распределением стека — размер стека по умолчанию в VC ++ составляет 1 МБ, у вас уже должно было быть переполнение стека.
В любом случае разумно ожидать другого использования памяти:
new Entry[size]
расходы size*sizeof(Entry)
байты плюс данные учета кучи (обычно это несколько полей целого размера); метод разделенных ассигнований стоит как минимум size*sizeof(Entry)
(размер всех «голых элементов») плюс size*sizeof(Entry *)
(размер массива указателей) плюс size+1
умножается на стоимость каждого размещения. Если мы примем 32-битную архитектуру со стоимостью 2 дюйма на выделение, вы быстро увидите, что это стоит size*24+8
байт памяти вместо size*12+8
для смежного массива в куче;size*28+8
то есть накладные расходы 16 байтов на каждый 12-байтовый элемент. Ваш код неверен, или я не вижу, как это работает. С 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>
,