18
\$\begingroup\$

The book Game Coding Complete, Fourth Edition, chapter 5 (Game Initialization and Shutdown), section Checking Memory contains this interesting code sample:

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;
    }
}

This raises some questions.

The first part just asks the OS (Windows) how much physical RAM is available. The curious part is the second one, which allocates a huge chunk of memory and frees it right away:

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

The author goes on to explain:

... this function allocates and immediately releases a huge block of memory. This has the effect of making Windows clean up any garbage that has accumulated in the memory manager and double-checks that you can allocate a contiguous block as large as you need. If the call succeeds, you've essentially run the equivalent of a Zamboni machine through your system's memory, getting it ready for your game to hit the ice...

But I have my reservations on that.

"Cleaning garbage that has accumulated in the memory manager?" Really? If the game has just started, shouldn't there be no garbage?

"Making sure you can allocate a contiguous block?" In the very specific case where you are going to manage memory yourself, this would them make some sense, but still, if you do allocate a lot of memory right of the bat, you pretty much make it impossible for any other application to run in the system while yours is on.

Also, isn't this likely to force the OS to commit all that memory, and as consequence evict a lot of memory to the swap disk space, slowing your app startup a lot?

Is this really a good practice?

\$\endgroup\$
  • 3
    \$\begingroup\$ Most modern OSes will do nothing whatsoever when an app allocates a large area of memory, they use optimistic allocation and do not actually do anything until you fill the memory, I cannot imagine this doing anything more than being a potentially slow no-op \$\endgroup\$ – Vality Jul 2 '14 at 12:54
  • \$\begingroup\$ Does clearing a long strip of ground in the jungle, carving a radio, headphones, etc out of wood result in airplanes landing and delivering supplies? \$\endgroup\$ – Dan Neely Jul 2 '14 at 13:35
  • 10
    \$\begingroup\$ Game Coding Complete contains a lot of nonsense paired with a lot of not-understanding-C++ and C-that-looks-a-bit-like-C++ (also demonstrated in this sample, checking the result of operator new for nullptr), if you allow me to say. Best thing you can do with that book is light your chimney. Allocating and freeing a large block of memory of course doesn't "clean up" memory. \$\endgroup\$ – Damon Jul 2 '14 at 15:02
  • \$\begingroup\$ @Damon, I didn't check, but I suspect they've at least overloaded the global new operator to return null instead of throwing bad_alloc. If they didn't, then yes, this code is even more nonsensical :P \$\endgroup\$ – glampert Jul 2 '14 at 19:26
  • 1
    \$\begingroup\$ @glampert: Even assuming that's the case, operator delete is required to accept nullptr and treat it as no-op. Any global overload that doesn't do that is broken. Which means it's nonsensical either way. Just like assuming that allocating a huge block of memory and releasing it will "magically" do something good. At best, it won't do any harm (most likely, since the pages aren't even touched... otherwise it may swap out some pages from your working set which you'll need to reload later). \$\endgroup\$ – Damon Jul 2 '14 at 20:54
12
\$\begingroup\$

One thing that pre-allocating a large chunk of memory could do, if your computer was low on RAM, might be to force the OS to free some extra space by swapping parts of other programs' memory space to the disk.

Since such swapping is generally a very slow operation that pretty much freezes your program while it's happening, there could be some advantage to ensuring that, if it's going to happen anyway, it will happen before your game starts rather than in the middle of gameplay.

That said, forcing a swap-to-disk like this is not exactly 100% reliable:

  • On many virtual memory implementations, merely allocating memory doesn't actually trigger any swapping; rather, it only happens when you first access each page of the memory you allocated. (This is certainly true on modern versions of Linux, with memory overcommit enabled, as it is by default; I don't know for sure how different versions of Windows handle it.)

    Thus, if you really want this code to "clean up memory", you should probably add a memset() call or equivalent to actually write data to every part of the array (or at least to the first physicalRAMNeeded bytes of it) before releasing it.

  • Also, forcing the OS to swap other programs out of the RAM like this doesn't mean that they'll stay out of it. Windows is a multitasking OS, and as soon as one of those swapped-out programs wants to run again and to use its swapped-out data, it will get swapped back in, potentially freezing your game again.

    Thus, this trick is generally only useful if the RAM is being eaten up by large chunks of data that are not being actively accessed (such as large documents left open in the background in some editing software). Web browsers tend to be particularly problematic in this regard, since, even if you're not actually using a web page opened in some background tab, the page may still have scripts running on it that force it to be kept in RAM.

    (There are ways for a program to actually tell the OS to lock a part of its memory space into RAM and prevent it from being swapped out, but these typically require elevated privileges. In any case, the code you posted does not appear to be using any such methods.)

Basically, this trick seems only useful in that relatively narrow borderline situation where the user would have enough RAM to run your game (and any other software actively running in the background) without swapping, but that RAM is currently filled with inactive and unused open files or other data that can and should be swapped to the disk while your game is running. In such situations, it may be effective in making your game seem smoother and more responsive, by replacing occasional small swap operations during gameplay with a single one during startup.

\$\endgroup\$
  • 1
    \$\begingroup\$ From my limited knowledge of low level memory management, I can say that the decision of whether or not to swap a specific page is much more complex, and takes many more factors into consideration than simply the amount of memory requested and the amount of memory available. Oversimplifying this, and generally relying on suppositions is not very wise. \$\endgroup\$ – Panda Pajama Jul 3 '14 at 9:06
  • 3
    \$\begingroup\$ I think it is important to remember that all of these are suppositions that may work, not work, or seem to work, but for completely unrelated reasons. As far as I know, none of the behaviors pointed out in this answer are documented, and it would be unwise to rely on them. An OS update, settings change, compiler change, or pretty much anything can make this stop working, or even have a negative impact on performance. \$\endgroup\$ – Panda Pajama Jul 3 '14 at 9:07
26
\$\begingroup\$

I don't know how old that writeup is, but I'd say it's pretty old. In modern Windows (XP and newer, but specially on 64-bit versions), I would say doing something like that will have little to negative impact on the startup of your game.

First of all, remember that the address space is virtualized for each process. As far as your process is concerned, you have the entire address space to yourself, and you will always get contiguous (virtual) memory, regardless of whether that memory is physically contiguous or not.

In fact, whether or not the memory is contiguous in physical memory is not something you have control over. The OS will choose how to allocate the physical blocks as it sees fit, and nothing you do at the application level can have effect on the benefits (if any) of having contiguous physical memory.

You do need to care about virtual memory fragmentation if you keep on allocating and freeing memory of different sizes for a very long amount of time. This is of course much less relevant with a 64-bit address space. Games don't usually run for months, so you most likely won't have to care about this, unless we're talking about long running game servers.

Even if you do allocate lots of memory of wildly different sizes in a 32-bit machine, modern memory allocations are pretty good, so it is unlikely you will get virtual memory fragmentation issues unless you're actively looking for them

To be honest with you, I don't know what that writer is going on about. Even if the address space was shared, and you did get a huge contiguous block in physical memory, by freeing it, you're saying you don't care about it anymore. There are other programs running on the background doing their own allocations, so even if your huge malloc succeeded, nobody will guarantee the next one will succeed as well.

I think that's a case of "it worked for some reason I don't really understand, so I made something up to explain it".

\$\endgroup\$
  • 10
    \$\begingroup\$ RAM is not like a rotating hard disk where having contiguous blocks is in any way "better" than not. This is NOT true. contiguous RAM access is an order of magnitude faster than random access. RAM chips are broken down into sections for which there is a certain amount of latency to switch, and more importantly, L1 and L2 cache misses will drastically affect your performance when your memory is scattered. \$\endgroup\$ – CaptainCodeman Jul 2 '14 at 7:48
  • \$\begingroup\$ @CaptainCodeman: I have to accept that goes out of my field of knowledge, but in any way, as a Windows -application- programmer you have no control over whether your memory is physically contiguous or not. Asking for a huge block of memory won't change much. \$\endgroup\$ – Panda Pajama Jul 2 '14 at 7:52
  • \$\begingroup\$ @CaptainCodeman actually have contiguous virtual block be in contiguous mod 2^N block in RAM will speed it up due to cache line allocation \$\endgroup\$ – ratchet freak Jul 2 '14 at 7:57
  • 8
    \$\begingroup\$ @CaptainCodeman Yeah, sequential DRAM access is faster, but we're talking about the virtual->physical mapping which happens in pages of 4 KB (or more), so whether that mapping is sequential or not shouldn't have much impact on memory read speeds. Plus, cache prefetching and stuff like that operates in virtual address space, not physical. \$\endgroup\$ – Nathan Reed Jul 2 '14 at 8:05
  • 1
    \$\begingroup\$ @NathanReed Fair enough, and thanks for the clarification. I was just expressing that RAM access speed is not entirely uniform across the memory space, as was suggested. \$\endgroup\$ – CaptainCodeman Jul 2 '14 at 8:46

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.