Last time, we took at a look at how whether a language is considered “interpreted” or “compiled” can impact the performance of your game project. Although this can have an impact on your game, it’s not the only aspect of a language that can. The other big feature of a language that can impact your game’s performance is how that language manages its memory. There are several different ways to manage memory and entire books have been written on the subject. In trying to keep this blog accessible, I won’t be going over every approach to memory management. Instead, we’ll take a look at the two major categories that memory management generally fall into: manual and automatic.
Memory Management: Manual vs. Automatic Memory Management
Manual memory management came first and requires that a programmer specifically request memory from the operating system for storage. Whenever a variable is declared in code, the instruction is effectively asking the OS for enough space in memory to hold the variable’s data type. Because all the variables are in the code when it’s compiled, they’re known at the time of compilation. This means we know how much space needs to be set aside for all the variables when the program is loaded into memory. But sometimes we don’t know how much memory we’re going to need. Maybe our program needs to open an external document and load it into memory so it can be displayed and manipulated. We could try to guess at the biggest possible file we’d ever need to load, but then we’d be setting aside all that space in memory on the off chance the document is even that big. Any unused space is effectively wasted. To keep executables small while still supporting the ability to be flexible, we have access to a second type of memory: the heap. The heap is a term referring to memory that’s allocated at run-time, also called dynamically allocated memory. Lower-level languages such as C and C++ have specific commands to request memory from the heap. When the command is run, that amount of memory is allocated to the program from outside the program and a handle to that memory is returned. That handle can be used to read and write to the memory on the heap. However, returning that memory when we’re done with it is the responsibility of the programmer. That’s where things can get tricky.
Because the programmer is responsible for dynamically allocated memory, mismanaging that handle can cause problems. The most common one is the memory leak. Remember: the heap effectively exists separately from the rest of our program memory. If we don’t tell the OS that we’re done with it, it will continue to leave it allocated to the program even if the program terminates or the variable holding the handle is reassigned. If the programmer isn’t careful about ensuring all the heap memory they requested is freed, the system will start to accumulate memory that has been allocated but not freed. If the program runs for long enough or is run often enough, the RAM in a system is gradually used up until there’s none left. The system will try to compensate, but will eventually run out of memory for basic operations and crash.
Alternatively, some languages are designed to manage memory automatically. Most languages still allow us to request memory from the heap, but doing so is a basic part of the language instead of something separate. That memory will also be tracked and released for us during execution. This frees up the programmer to just focus on writing code that works, rather than dealing with memory. In general, this is great but it does come at a cost (sound familiar?). Automatic memory management requires that some system is monitoring and managing the memory. This system requires resources to run, resulting in increased overhead in terms of both memory and processing time. The automatic memory management algorithms are also only so effective. As programmers, we know exactly when we’re done with memory, but an automated system doesn’t. They’re designed to be more general, which means memory isn’t always freed up as soon as a program is done with it. We have to wait for the memory management algorithm to determine that the memory should be freed. If your game is creating and deleting a lot of objects, you’ll find a portion of the memory used will linger for a little bit, resulting in a bigger memory footprint than your program strictly needs. In the worst case, the memory management system may actually halt execution of your game while it searches through the program’s current memory space for any memory that can be freed. If your game isn’t using a lot of memory, you may not even notice when this happens. If it is, you may see your game stall until the memory management is done. In console game development, this generally isn’t considered a great option. Languages with manual memory management such as C++ tend to be more popular for console game development because developers can utilize more of the available processor time and RAM for the game itself instead of managing memory. That means more objects and better framerates.
Now that we’ve covered two of the biggest language features that can impact game performance, next week we’ll take a look at some of the more common languages available to write games in and how they factor into the world of game development. See you then!