Почему удаленная память не может быть повторно использована - PullRequest
7 голосов
/ 24 марта 2011

Я использую C ++ в Windows 7 с MSVC 9.0, а также смог протестировать и воспроизвести на Windows XP SP3 с MSVC 9.0.

Если я выделяю 1 ГБ объектов размером 0,5 МБ, при их удалении все в порядке и ведет себя так, как ожидалось.Однако, если при удалении я выделяю 1 ГБ объектов размером 0,25 МБ, память остается зарезервированной (желтый * Монитор адресного пространства ), и с этого момента будет использоваться только для ресурсов, размер которых меньше 0,25 МБ..

Этот простой код позволит вам протестировать оба сценария, изменив, какая структура является typedef'd.После того, как он выделил и удалил структуры, он выделит 1 ГБ из 1 МБ буферов символов, чтобы посмотреть, будут ли буферы символов использовать память, которую когда-то занимали структуры.

struct HalfMegStruct
{
    HalfMegStruct():m_Next(0){}

    /* return the number of objects needed to allocate one gig */
    static int getIterations(){ return 2048; }

    int m_Data[131071];
    HalfMegStruct* m_Next;
};

struct QuarterMegStruct
{
    QuarterMegStruct():m_Next(0){}

    /* return the number of objects needed to allocate one gig */
    static int getIterations(){ return 4096; }

    int m_Data[65535];
    QuarterMegStruct* m_Next;
};

// which struct to use
typedef QuarterMegStruct UseType;

int main()
{
    UseType* first = new UseType;
    UseType* current = first;

    for ( int i = 0; i < UseType::getIterations(); ++i )
        current = current->m_Next = new UseType;

    while ( first->m_Next )
    {
        UseType* temp = first->m_Next;
        delete first;
        first = temp;
    }

    delete first;

    for ( unsigned int i = 0; i < 1024; ++i )
        // one meg buffer, i'm aware this is a leak but its for illustrative purposes. 
        new char[ 1048576 ]; 

    return 0;
}

Ниже вы можете увидеть мои результатыиз Монитор адресного пространства .Позвольте мне подчеркнуть, что единственная разница между этими двумя конечными результатами заключается в размере структур, выделяемых до маркера в 1 ГБ .

Quarter Meg Half Meg

Это кажется мне довольно серьезной проблемой, от которой многие могут страдать и даже не знать об этом.

  • Так это по замыслу или это следует считать ошибкой?
  • Можно ли сделать небольшие удаленные объекты фактически свободными для использования при больших выделениях?
  • Ибольше из любопытства, Mac или Linux машина страдают от той же проблемы?

Ответы [ 5 ]

9 голосов
/ 24 марта 2011

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

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

Обратите внимание, что хотя это может показаться странным, в большинстве программ (не тестовых, а реальных) шаблоны использования памяти повторяются: если вы запрашивали 100 тыс. Блоков один раз, это чаще всего имеет место.что ты сделаешь это снова.А сохранение памяти зарезервировано может повысить производительность и фактически уменьшить фрагментацию, которая будет исходить от всех запросов, поступающих из одного и того же сегмента.

Вы можете, если хотите,потратьте немного времени, узнайте, как работает ваш распределитель, проанализировав поведение.Напишите несколько тестов, которые получат размер X, освободят его, затем приобретут размер Y и затем покажут использование памяти.Зафиксируйте значение X и играйте с Y. Если запросы на оба размера удовлетворяются из одних и тех же сегментов, у вас не будет зарезервированной / неиспользуемой памяти (изображение слева), а когда размеры будут предоставляться из разных сегментов, вы увидитеэффект на изображении справа.

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

2 голосов
/ 24 марта 2011

Я могу подтвердить то же поведение с g ++ 4.4.0 под Windows 7, так что его нет в компиляторе. На самом деле, программа завершается ошибкой, когда getIterations() возвращает 3590 или более - вы получаете такое же ограничение? Это похоже на ошибку в распределении системной памяти Windows. Хорошо осведомленным душам хорошо говорить о фрагментации памяти, но здесь все удалено, поэтому наблюдаемое поведение определенно не должно происходить.

1 голос
/ 25 марта 2011

Я говорил с некоторыми авторитетами по этому вопросу (Грег, если ты там, скажи привет; D) и могу подтвердить, что то, что говорит Дэвид, в основном правильно.

По мере роста кучи во время первого прохода выделения ~ 0,25 МБ объектов, куча резервирует и фиксирует память. По мере сокращения кучи на этапе удаления, она расходуется с некоторой скоростью, но не обязательно освобождает диапазоны виртуальных адресов, зарезервированные на этапе выделения. В последнем проходе распределения выделения в 1 МБ обходят кучу из-за их размера и, таким образом, начинают конкурировать с кучей за VA.

Обратите внимание , что куча резервирует ВА, а не поддерживает его. VirtualAlloc и VirtualFree могут помочь объяснить, если вам интересно. Этот факт не решает проблему, с которой вы столкнулись, а именно: процесс исчерпал виртуальное адресное пространство .

1 голос
/ 24 марта 2011

Используя ваш код, я выполнил ваш тест и получил тот же результат. Я подозреваю, что Дэвид Родригес прав в этом деле.

Я выполнил тест и получил тот же результат, что и вы. Похоже, что может происходить такое "ведро".

Я тоже пробовал два разных теста. Вместо того, чтобы выделять 1 ГБ данных с использованием буферов 1 МБ, я выделил так же, как память была впервые выделена после удаления. Во втором тесте я выделил очищенный буфер размером в полмегабайта, затем выделил буфер размером в четверть мегабайта, добавив до 512 МБ для каждого. В конце оба теста имели одинаковый результат с памятью, только 512 выделяется без большого куска зарезервированной памяти.

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

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

0 голосов
/ 25 марта 2011

Это побочный эффект кучи низкой фрагментации.

http://msdn.microsoft.com/en-us/library/aa366750(v=vs.85).aspx

Вы должны попробовать отключить его, чтобы посмотреть, поможет ли это. Запускайте как GetProcessHeap, так и кучу CRT (и любые другие кучи, которые вы могли создать).

...