Optimizing Data Segment Usage
Read/Write Data Sections
Any item that has greater than function-level scope or is declared with the static keyword resides in the read/write data sections of the executable file. In addition, the loader requires access to something less 100 bytes of this data area.
There are two kinds of read/write data in your program: The initialized items live in the .data section, and the unitilialized items live in the .bss section. These sections share a region of memory, so the sum of their length is what matters, not the individual sizes. Here are their lines in the map file:
Start Length Name Class 0003:000000a8 00000010H .data DATA 0003:000000b8 00000810H .bss DATA
Porting Tip: Here is a good, rough-cut checklist for squeezing wasted memory out of your application's executable file:
- Explicitly declare all data that is functionally constant by using the const keyword.
- Recompile and see how use of const changes the size of the sections.
- Adjust the size of the read/write data section: Shrink it by moving invariant data to const and other data to the stack. Fill it by using leftover space for buffering that otherwise would have been allocated from the heap.
- Leave 100 free bytes in the read/write section for use by the loader.
- Use CE-style LoadString() to access string resources in place.
- Put comments in all your source files that remind you to look at a load map if you change code.
Low Memory Conditions
Even when we've done all we can to make our program memory efficient, memory can become dangerously low. When making allocation requests, you must assume that they can fail, and make provisions for handling the failure gracefully. You should always test the return value of a memory allocation function.
Porting Tip: Search your code for allocation function calls, and make sure all returns are tested. Provide a handler for graceful cleanup if allocation fails.
Running out of memory is a more serious (and more likely) situation on CE than it is on any other Windows platform. There is no disk-based virtual memory to swap pages to, so the operating system constantly monitors the allocation status of physical memory. CE uses several mechanisms to conserve memory as it becomes more scarce, but for our purposes two of these are important: allocation request filtering and the WM_HIBERATE construct.
Allocation Request Filtering
Allocation request filtering prevents a single application from taking all available memory with a single large allocation request. Basically, it does this by enforcing a flexible upper limit on the amount of memory and application can request. As memory becomes scarcer, the system lowers the limits on maximum allocation size.
You may see the effects of allocation filtering if you attempt to make an allocation with VirtualAlloc() and it fails even though the result of a call to VirtualQuery() seems to show that there is enough free memory to satisfy your request. In fact, in any kind of low-memory situation, VirtualAlloc family functions will fail first, so always test returns and provide a failure handler.
Warning Applications About Low Memory With the WM_HIBERNATE Message
Scarcity of memory is a situation that is always looming on a CE device. To make the memory-constrained environment manageable, three things need to happen:
- Applications must strive to be memory efficient.
- The system must notify applications when a memory crisis is developing.
- The applications must have a chance to clean up and gracefully relinquish whatever memory they can.
The WM_HIBERNATE message is CE's way of giving notice to applications that the system is entering a low-memory state. For a notification about a memory shortage to be useful, you must get it in time to do something about it. For example, the application may need to tell the user to close files, to relinquish whatever memory it can, and to save its state in preparation for an orderly close. For this reason, applications begin to get the WM_HIBERNATE message when available memory falls below the "hibernation threshold" which is about 128 Kb (this threshold is set slightly higher if the page size is greater than 1 Kb). At 64 Kb of free memory, we cross the "low memory" threshold, and the system more aggressively requests active applications to hibernate, attempts to free pages in the system heap, and as a last resort, and tries to shrink the stack. Finally, at about 16 Kb, the system displays the "out of memory" dialog and begins forcibly closing down applications.
When an application first receives the hibernate notification, chances are good that you can clean up, relinquish memory, and prepare for possible close. In all likelihood, you'll still be able to allocate small amounts of memory from your local heap—remember, WM_HIBERNATE is sent as a result of a page allocation crisis, but that doesn't mean that all committed pages are full. If you design your code so that you are never dependent (or at least not for long) on holding large allocations of memory, you can respond to WM_HIBERNATE by completing operations that are in progress, freeing memory, and possibly saving application state.
Porting Tip: If you have been reasonably careful about your use of allocated memory, responding to WM_HIBERNATE may not be too difficult.
When you get a WM_HIBERNATE message, your first response should be to free any allocation made with VirtualAlloc(). These go back to the allocation pool immediately, and so may resolve the immediate crisis. Next, if possible, finish any pending operations that make use of a private heap, and relinquish the heap. Freeing memory devoted to a private heap also returns pages to the allocation pool immediately, so freeing a private heap may be worth incurring some data loss.
Graphic objects, especially bitmaps, consume large amounts of memory, so discard those, along with created brushes, pens, and device contexts next.
If you want to warn the user that a low memory situation exists when you first get the WM_HIBERNATE message, you can invoke the system low memory dialog using this function:
int SHShowOutOfMemory(HWND hwndOwner, UINT grfFlags);
The parameters, in the order shown, are the handle to the window that will serve as the parent of the dialog, and a reserved value that must be set to zero. Also, though the declaration shows that there is an integer return, there is no meaningful value returned. The advantage of using this function is that it comes to you with absolutely no overhead. Putting this dialog up may cause the user to close another application, which may in turn solve your memory shortage problems. At the very least, it provides feedback and won't consume additional memory.
In the next installment, you'll see how to use CE's memory reconnaissance tools to monitor you application's exposure to low-memory conditions.
About the Author
Nancy Nicolaisen is a software engineer who has designed and implemented highly modular Windows CE products that include features such as full remote diagnostics, CE-side data compression, dynamically constructed user interface, automatic screen size detection, entry time data validation.
In addition to writing for Developer.com, she has written several books including Making Win 32 Applications Mobile.
# # #
Page 2 of 2