Почему куча после выделения массива такая большая - PullRequest
3 голосов
/ 22 июля 2011

У меня есть очень простое приложение, которое сводится к следующему коду:

char* gBigArray[200][200][200];
unsigned int Initialise(){  
    for(int ta=0;ta<200;ta++)
        for(int tb=0;tb<200;tb++)
            for(int tc=0;tc<200;tc++)
                gBigArray[ta][tb][tc]=new char;
    return sizeof(gBigArray);
}

Функция возвращает ожидаемое значение 32000000 байт, что составляет приблизительно 30 МБ, однако в диспетчере задач Windows (и при условии, что она не точна на 100%) выдает Память (частный рабочий набор) значение около 157MB. Я загрузил приложение в VMMap от SysInternals и имею следующие значения:

Я не уверен, что означает Image (указан в разделе «Тип»), хотя не имеет значения, что его значение примерно соответствует ожидаемому. Что на самом деле выбрасывает для меня, так это значение кучи, из которого происходят очевидные огромные размеры.

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

Ответы [ 8 ]

4 голосов
/ 22 июля 2011

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

2 голосов
/ 22 июля 2011

Вы выделяете один символ за раз.Обычно для каждого выделения выделяется пространство

Выделите память на один большой блок (или, по крайней мере, на несколько блоков)

1 голос
/ 22 июля 2011

Owww!Мои встраиваемые системы перевернутся и умрут, если столкнутся с этим кодом.Каждое распределение имеет довольно много дополнительной информации, связанной с ним, и либо распределяется до фиксированного размера, либо управляется через объект типа связанного списка.В моей системе этот новый 1 символ станет 64-байтовым распределением из небольшого распределителя объектов, так что управление будет происходить за O (1) времени.Но в других системах это может легко ужасно фрагментировать вашу память, запускать последующие новые и удаления очень медленно O (n), где n - количество отслеживаемых вещей, и, как правило, обрекает приложение на какое-то время, так как каждый символ становится равнымкак минимум 32-байтовое выделение и размещение во всевозможных «дырявых» дырах в памяти, тем самым выталкивая вашу кучу выделения намного дальше, чем вы ожидаете.

Сделайте одно большое выделение и сопоставьте свой 3D-массив с ним, если вынадо с размещением новой или другой хитрости указателя.

1 голос
/ 22 июля 2011

Не забывайте, что char* gBigArray[200][200][200]; выделяет место для 200*200*200=8000000 указателей, каждый размер слова.Это 32 МБ в 32-битной системе.

Добавьте к этому еще 8000000 char еще 8 МБ.Поскольку вы распределяете их по одному, вероятно, они не могут распределить их по одному байту на элемент, поэтому они, вероятно, также примут размер слова на элемент, что приведет к другим 32 МБ (32-разрядная система).

Остальныеэто, вероятно, накладные расходы, что также важно, поскольку система C ++ должна помнить, сколько элементов содержится в массиве, выделенном с помощью new, для delete [].

0 голосов
/ 10 апреля 2013

Отредактировано из вышеприведенного поста в вики-пост сообщества :

Как показывают ответы ниже, проблема в том, что я создаю новый символ 200 ^ 3 раза, и хотякаждый символ занимает всего 1 байт, для каждого объекта в куче накладные расходы.Кажется, создание массива символов для всех символов сбивает память с уровня правдоподобия:

char* gBigArray[200][200][200];
char* gCharBlock=new char[200*200*200];
unsigned int Initialise(){  
    unsigned int mIndex=0;
    for(int ta=0;ta<200;ta++)
        for(int tb=0;tb<200;tb++)
            for(int tc=0;tc<200;tc++)
                gBigArray[ta][tb][tc]=&gCharBlock[mIndex++];
    return sizeof(gBigArray);
}
0 голосов
/ 22 июля 2011

30 МБ - для указателей .Остальное относится к хранилищу, выделенному вам с помощью вызова new, на который указатели указывают на .Компиляторам разрешено выделять более одного байта по разным причинам, например, для выравнивания по границам слов или для увеличения пространства на случай, если вы захотите это позже.Если вы хотите использовать символы размером 8 МБ, оставьте * в объявлении на gBigArray.

0 голосов
/ 22 июля 2011

Может быть, это проблема памяти? Какой размер пробелов между значениями?

0 голосов
/ 22 июля 2011

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

...