Распределяет ли и освобождает ли огромный кусок памяти при запуске «очистку памяти»?

18

Книга Game Coding Complete, четвертое издание , глава 5 ( Инициализация и завершение игры ), раздел Проверка памяти, содержит этот интересный пример кода:

bool CheckMemory(const DWORDLONG physicalRAMNeeded, const DWORDLONG virtualRAMNeeded)
{
    MEMORYSTATUSEX status; 
    GlobalMemoryStatusEx(&status);
    if (status.ullTotalPhys < physicalRAMNeeded) 
    {
        // you don’t have enough physical memory. Tell the player to go get a 
        // real computer and give this one to his mother. 
        GCC_ERROR("CheckMemory Failure: Not enough physical memory."); 
        return false;
    }
    // Check for enough free memory.
    if (status.ullAvailVirtual < virtualRAMNeeded) 
    {
        // you don’t have enough virtual memory available.
        // Tell the player to shut down the copy of Visual Studio running in the 
        // background, or whatever seems to be sucking the memory dry. 
        GCC_ERROR("CheckMemory Failure: Not enough virtual memory.");
        return false;
    }

    char *buff = GCC_NEW char[virtualRAMNeeded]; 
    if (buff)
    {
        delete[] buff;
    }
    else
    {
        // even though there is enough memory, it isn't available in one
        // block, which can be critical for games that manage their own memory 
        GCC_ERROR("CheckMemory Failure: Not enough contiguous memory."); 
        return false;
    }
}

Это поднимает некоторые вопросы.

Первая часть просто спрашивает ОС (Windows), сколько физической памяти доступно. Любопытная часть - вторая, которая выделяет огромный кусок памяти и освобождает ее сразу же:

char *buff = GCC_NEW char[virtualRAMNeeded]; 
if (buff)
{
    delete[] buff;
}

Автор продолжает объяснять:

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

Но у меня есть оговорки на этот счет.

"Очистка мусора, накопившегося в диспетчере памяти?" В самом деле? Если игра только началась, разве не должно быть мусора?

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

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

Это действительно хорошая практика?

glampert
источник
3
Большинство современных ОС ничего не делают, когда приложение выделяет большую область памяти, они используют оптимистичное распределение и фактически ничего не делают, пока вы не заполняете память. Я не могу себе представить, что это делает что-то большее, чем быть потенциально медленным
неактивным
Очищает ли длинная полоса земли в джунглях, вырезает радио, наушники и т. Д. Из-за того, что самолеты приземляются и доставляют припасы?
Дэн Нили,
10
Game Coding Complete содержит много бессмыслицы в сочетании с множеством непонятных C ++ и C-that-look-a-чуть-чуть-как-C ++ (также продемонстрированных в этом примере, проверяя результат operator newдля nullptr), если вы позволите мне сказать. Лучшее, что вы можете сделать с этой книгой, это зажечь дымоход. Выделение и освобождение большого блока памяти, конечно , не «очищает» память.
Деймон
@ Дэймон, я не проверял, но я подозреваю, что они по крайней мере перегружают глобальный newоператор, чтобы возвращать ноль вместо броска bad_alloc. Если они этого не сделали, то да, этот код еще более бессмысленный: P
glampert
1
@glampert: Даже если предположить, что это так, operator deleteтребуется принять nullptrи рассматривать его как неоперативный. Любая глобальная перегрузка, которая не делает этого, сломана. Что означает, что это бессмысленно в любом случае. Точно так же, как предположение, что выделение огромного блока памяти и освобождение его «волшебным образом» принесут пользу. В лучшем случае это не принесет никакого вреда (скорее всего, поскольку страницы даже не затрагиваются ... в противном случае это может поменять некоторые страницы из вашего рабочего набора, которые вам необходимо будет перезагрузить позже).
Деймон

Ответы:

12

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

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

Тем не менее, принудительное переключение на диск не является абсолютно надежным:

  • Во многих реализациях виртуальной памяти простое распределение памяти фактически не вызывает никакой подкачки; скорее это происходит только при первом доступе к каждой странице выделенной памяти. (Это, безусловно, верно в современных версиях Linux с включенной перегрузкой памяти, как это происходит по умолчанию; я не знаю точно, как разные версии Windows справляются с этим.)

    Таким образом, если вы действительно хотите, чтобы этот код «очищал память», вам, вероятно, следует добавить memset()вызов или его эквивалент для фактической записи данных в каждую часть массива (или, по крайней мере, в его первые physicalRAMNeededбайты) перед его освобождением.

  • Кроме того, принуждение ОС к замене других программ из оперативной памяти таким образом не означает, что они останутся вне этой памяти. Windows является многозадачной ОС, и как только одна из этих замененных программ захочет снова запустить и использовать свои замененные данные, она снова будет заменена, что потенциально может снова заморозить вашу игру.

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

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

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

Илмари Каронен
источник
1
Исходя из моих ограниченных знаний о низкоуровневом управлении памятью, я могу сказать, что решение о том, стоит ли менять конкретную страницу или нет, гораздо сложнее и учитывает гораздо больше факторов, чем просто объем запрашиваемой памяти и объем доступной памяти. , Упрощать это и вообще полагаться на предположения не очень мудро.
Панда Пижама
3
Я думаю, что важно помнить, что все это предположения, которые могут работать, а не работать или казаться работающими, но по совершенно не связанным причинам. Насколько я знаю, ни одно из поведений, указанных в этом ответе, не задокументировано, и было бы неразумно полагаться на них. Обновление ОС, изменение настроек, изменение компилятора или почти все, что угодно, могут заставить это перестать работать или даже оказать негативное влияние на производительность.
Панда Пижама
26

Я не знаю, сколько лет этой записи, но я бы сказал, что она довольно старая. Я бы сказал, что в современных Windows (XP и новее, но особенно в 64-битных версиях) выполнение подобных действий мало повлияет на запуск вашей игры.

Прежде всего, помните, что адресное пространство виртуализируется для каждого процесса. Что касается вашего процесса, у вас есть все адресное пространство для себя, и вы всегда будете получать непрерывную (виртуальную) память, независимо от того, является ли эта память физически непрерывной или нет.

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

Вам нужно заботиться о фрагментации виртуальной памяти, если вы продолжаете выделять и освобождать память разных размеров в течение очень длительного периода времени. Это, конечно, гораздо менее актуально для 64-битного адресного пространства. Игры обычно не запускаются месяцами, поэтому вам, скорее всего, не придется об этом заботиться, если только мы не говорим о долго работающих игровых серверах.

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

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

Я думаю, что это тот случай, когда «это сработало по какой-то причине, которую я не совсем понимаю, поэтому я придумал что-то, чтобы объяснить это».

Панда Пижама
источник
10
Оперативная память не похожа на вращающийся жесткий диск, где наличие смежных блоков в любом случае «лучше», чем нет. Это неправда. непрерывный доступ к ОЗУ на порядок быстрее, чем произвольный доступ. Микросхемы ОЗУ разбиты на секции, для которых существует определенная задержка переключения, и, что более важно, пропуски кеша L1 и L2 сильно повлияют на вашу производительность, когда ваша память будет разбросана.
CaptainCodeman
@CaptainCodeman: Я должен признать, что это выходит за рамки моей области знаний, но в любом случае, как программист по Windows-приложениям, вы не контролируете, является ли ваша память физически смежной или нет. Просьба об огромном блоке памяти мало что изменит.
Панда Пижама
@CaptainCodeman на самом деле имеет непрерывный виртуальный блок, находящийся в непрерывном моде 2 ^ N блок в ОЗУ ускорит его из-за выделения строк кэша
трещотка урод
8
@CaptainCodeman Да, последовательный доступ к DRAM быстрее, но мы говорим о виртуальном-> физическом отображении, которое происходит на страницах размером 4 КБ (или более), поэтому то, является ли это отображение последовательным или нет, не должно сильно влиять на память скорость чтения. Кроме того, предварительная выборка из кеша и тому подобное работает в виртуальном адресном пространстве, а не в физическом.
Натан Рид
1
@ NatanReed Справедливо, и спасибо за разъяснения. Я только что говорил, что скорость доступа к ОЗУ не совсем одинакова во всем пространстве памяти, как было предложено.
CaptainCodeman