Improving the Performance of Windows 95 Games

Peter Donnelly
Microsoft Corporation

Peter Donnelly has been a game designer since 1972 and a programmer since 1984. He has authored four "paper" games with historical and fantasy themes, and his computer design credits include three interactive comic-book adventures and one full-scale adventure game. He has also developed shareware games and game editors for MS-DOS and Windows.

December 6, 1996

Click to open or copy the sample code associated with this technical article.

Abstract

A key issue in the development of real-time games for the Windows® 95 environment is the prevention of stalls or stutters in the game loop as Windows performs some peripheral task. In "Moving Your Game to Windows, Part I" I described some techniques for accurately pacing events and giving greater priority to time-critical threads. In this article I suggest solutions to other stalling problems caused by system events.

Code Memory Paging

Problem: Game performance slows down when some functions are called.

Cause: The application resides on CD-ROM, and access times are slow. Not all functions can fit into RAM, and those not recently used may have to be read from the CD when called.

Solution: Load the entire application to virtual memory. That way, when functions are discarded from RAM they are put in the paging file (commonly called the swapfile), whence they are more quickly retrieved than from the CD. Raymond Chen's sample code in DMDFM.C will load all or part of any application into memory. The sample program, LOADAPPTEST.C, loads all of itself into memory when you press F2. (To view or copy the sample files, click the hotlink at the top of this article.)

CD-ROM Caching

Problem: Applications may actually be slowed down by the CD-ROM cache.

Cause: The read-ahead cache allocates memory for functions that may not necessarily be used. The result is that functions already in RAM may have to be paged out.

Solution: Load the application to the swapfile as explained in the previous section.

In general, you should not attempt to implement your own CD-ROM caching system. It is likely to interact with the system cache in ways that interfere with performance.

Data Memory Paging

Problem: Performance slows down as data is accessed from the swapfile, even though the application has attempted to load all data into memory by walking through it and "touching" it.

Cause: The explanation requires some understanding of how Windows decides when a block of memory should be discarded or paged out, i.e., moved from RAM to the swapfile.

The Windows 95 memory manager includes a safeguard against processes that use a block of memory once and then forget about it. Consider, for example, a large bitmap used as desktop wallpaper. The user has been running a maximized application, so that the bitmap has long since been paged out. Then the user minimizes the application for a moment. Now the system needs to reload the wallpaper bitmap so it can paint it onto the screen. Without a safeguard, the bitmap would elbow aside a lot of other pages from RAM, even though they might contain code or data that will be needed again as soon as the application is restored. The bitmap is unlikely to be needed again for some time, but it is now hogging physical memory.

The safety mechanism prevents this from happening in the following way. When the system loads in a page (a page being 4 kilobytes on x86 machines), it first leaves it in a state of limbo. While it is in this state, the page never acquires a "last accessed" timestamp, even if it is accessed again. It is promoted to full status only when 16 more pages (in Windows 95) have been loaded. But at this point it is marked as not having been recently used, so it is still a prime candidate for disposal the next time physical memory is needed. Only if the page is accessed after promotion to full memory status does Windows mark it as recently used, thus recognizing it as potentially useful and worth keeping in physical memory.

In the case of the desktop wallpaper example, this means that the first page loaded is likely to be discarded after 16 more pages have been loaded. As each new page is loaded, another one is promoted from limbo and one is discarded. The result is that loading an image of any size requires the replacement of as little as 17 x 4 = 68K of physical memory. (Again, the numbers are valid for Windows 95.)

Any attempt to force Windows to keep a large block of data in physical memory by simply walking through it page by page will fail, because the safety mechanism treats the data just like the bitmap in our example.

Solution: In order to have Windows recognize a page of memory as being too important to discard, you must touch it again as soon as it has been promoted from limbo. (Touching it before it is promoted will not have the desired effect.) The following function will accomplish this:

/*
  Assume that pb and cb are both rounded to page boundaries
  and that PageSize has been determined from GetSystemInformation().
*/
#define cbLag  (16 * PageSize)

void MakeRegionPresent(volatile BYTE *pb, UINT cb)
  {
  UINT ib;

  Assert(((DWORD)pb & (PageSize - 1)) == 0);
  Assert(((DWORD)cb & (PageSize - 1)) == 0);

  for (ib = cbLag; ib < cb; ib += PageSize)
    {
    pb[ib - cbLag];
    pb[ib];
    }
  }

The Five-Minute Pause

Problem: With certain configurations, the system suspends all other business every five minutes while it polls the network.

Cause: The problem occurs, in general, when DHCP (dynamic host configuration protocol; see the bibliography for further information) is installed on a network adapter (possibly the dial-up network adapter; i.e., modem), but the adapter is not on a network that has a DHCP server. This often happens when the user has accidentally enabled DHCP on the physical network adapter but meant to enable it on the dial-up adapter. It also happens when the user has enabled DHCP on the modem, but the Internet service provider doesn't use DHCP.

Solution: Tell the user to change the network settings according to the following instructions:

In Control Panel, double-click Network. From the component list on the Configuration page, click TCP/IP and then click the Properties button. On the IP Address tab, click Specify an IP Address and then enter the IP address and subnet mask for your computer. (You can obtain these numbers by running WINIPCFG.)

Swapfile Cleanup

Problem: Windows decides it is time to clean up the swapfile and embarks on this task, bringing other applications to their knees.

Cause: Windows decides to start housekeeping when it sees that all applications are idle, i.e., sitting on GetMessage.

Solution: As long as your application appears to be busy, Windows will postpone its housekeeping tasks. Don't use any blocking GetMessage calls—use PeekMessage instead, to keep the message pump going.

The following code fragment shows one way to use PeekMessage instead of GetMessage in the main message loop. Assume that the AppPaused Boolean is set to TRUE whenever the application loses the focus. This flag ensures that the app won't eat up CPU time by continuing to run the PeekMessage loop even when it is theoretically idle.

MSG  msg;

do
  {
  if (PeekMessage(&msg, NULL, 0, 0, PM_REMOVE))
    {
    if (msg.message == WM_QUIT) break;  // the only way out of the loop
    TranslateMessage(&msg);
    DispatchMessage(&msg);
    }
  else
    {
    if (AppPaused) WaitMessage();
    else
      {
      // do any non-message-based processing here, e.g. animation
      }
    }
  }
while (TRUE);
return msg.wParam;

Bibliography

Microsoft Corporation, "Configuring TCP/IP with DHCP" in Windows 95 Resource Kit, MSDN Library.

Microsoft Corporation, "DHCP (Dynamic Host Configuration Protocol) Basics" in Windows for Workgroups and Windows NT Knowledge Base.