How Big Is My Program?

Home
Back To Tips Page

This is a common question, and one which is profoundly misunderstood. The most important answer is: it doesn't matter. What matters is how big your data is. Thinking about "how big" the code is almost always leads to a lot of effort that produces no payback whatsoever. Some of this is covered in my essay, Optimization: Your Worst Enemy, where I point out that "traditional" approaches to "optimizing" a program are completely without merit in most cases, and the most important thing you can do is figure out where the real costs are, and optimize those.

The most common error I see is a misinterpretation of the data returned by the Task Manager or Process Viewer. The question is usually expressed as "I created this (small, empty, tiny) application and ran it and it takes (500KB, 1.7MB, 3.1MB...), how can I make this smaller?" The answer is, don't bother. Forget it. You are wasting your time.

The problem is that traditional programming style has taught programmers that "smaller is better" and trivial programming systems have presented programmers with a single number that is a "measure of goodness" where the smaller the number, the better the program. This applies only to applications running in trivial contexts. For example, if you have an 8K EPROM to hold your code on an embedded system, you know you have 8,192 bytes of code space, and you may have to play all sorts of sneaky tricks to fit your code into that size. If you have a 128K EPROM, most of those issues disappear, and you can concentrate on solving the real problem. In all cases, our goals in building systems is to reduce the need to concentrate on useless problems so we can focus on the problems we are trying to solve.

Virtual memory is one such methodology that allows us to ignore a lot of issues. Let's ignore data space for a moment, because that actually has genuine problems we need to concentrate on, and just consider code space.

What you can see, at best, using the process viewer or task manager, is the size of a the address space in use. But what difference does this make? If you have a job to do, it takes a certain amount of code to do it. The "optimizations" you might make to reduce code size have virtually no impact on the actual resulting code size. So stop worrying about it. If you really care about the size of a module, you can use the depends program (in the Visual Studio\Common\Tools directory of the VC++ distribution), and it will tell you the "size" of the module, which is still pretty uninformative, and besides, there isn't anything you can really do about it unless you have some massive static arrays (and if you do, you probably need them anyway, but that's part of the data problem, which is real).

But what are those large code sizes you see in the task manager? What's this 3.7MB MFC image? Isn't this because the tools are sloppy, Microsoft loves code bloat, and the library is overblown?

No.

The code is there to do a job. Consider the following program:

int main()
 {
   while(1);
   return 0;
  }

If you compile this and run it (note that it contains an infinite loop so you can see it running), it takes 548K. How could a program this small take 548K?

The answer is that you are confusing address space with program size. In order to run, this program requires both the NTDLL.DLL and the KERNEL32.DLL modules be mapped into the address space. What you're really seeing are the complete address space footprint of these modules. But these modules are mapped into every application. You might not touch more than a few pages of these libraries. So do they add to the size of your program? Not in the slightest. They're already loaded, they're mapped everywhere, and they are probably used so often they never get paged out. So right away we see that there is essentially no meaning to the "program size" reported by the process manager or process viewer tools.

Now consider a full-blown MFC application, such as one that I wrote recently. It uses the following DLLs:

1
ADVAPI32.DLL
Registry support
2
COMCTL32.DLL
Common controls (spin control)
3
COMDLG32.DLL
Common dialogs (file open/save)
4
CTSYS32.DLL
MIDI support
5
DEVCON32.DLL
Multimedia support
6
GDI32.DLL
Graphics library
7
KERNEL32.DLL
Kernel support
8
MFC42.DLL
MFC support
9
MPR.DLL
Network support (probably required by MFC42.DLL)
10
MSACM32.DLL
Audio compression manager
11
MSACM32.DEV
Audio compression manager
12
MSIDLE.DLL
WebCheck support (probably required by MFC42.DLL)
13
MSVCRT.DLL
C runtime
14
NETAPI32.DLL
Network and browser support
15
NETRAP.DLL
RAP support
16
NETUI0.DLL
Registry and NLS support
17
NETUI1.DLL
Network services support
18
NTDLL.DLL
NT API
19
NTLANMAN.DLL
Network support
20
NTSHRUS.DLL
Shell support
21
OLE32.DLL
ActiveX support
22
RPCLTC1.DLL
RPC support
23
RPCRT4.DLL
RPC support (called by ADVAPI32.DLL functions)
24
SAMLIB.DLL
Security manager
25
SHELL32.DLL
Shell functions
26

(the actual program executable appear here in the list)

 
27
USER32.DLL
Menus, windows, etc.
28
WINMM.DLL
Multimedia library (MIDI)

Now I don't actually use a lot of these features; I get the full multimedia library even though I'm only doing MIDI and WAV output; I probably get other DLLs mapped in because I'm using the DLL version of the MFC library, MFC42.DLL, and it loads DLLs that load other DLLs. But if I need the code to do the job, I have to have it somewhere, and it might as well be a library that somebody else maintains. And if I'm not using the code, it really doesn't cost me anything to have it mapped into my address space (well, it costs me address space slots, and I do have a limit of something like 524,288 of those (the 2GB user address space), so the fact that a trivial program consumes 128 of these or so, or about 0.024% of them, on the whole isn't going to matter a lot. The above program consumed 3.7MB of address space, slightly over 900 pages, or 0.17% of my address space. So why should I care?)

Working Set

A long time ago, back in the late 1960s, the notion of the working set evolved as a way of characterizing the behavior of a virtual memory program. The working set represents the set of pages recently accessed. So it doesn't matter how large your code size or data size are, if the working set is small. Since most of the pages, such as those in the above DLL list, are largely untouched, they are not part of the working set, and their presence in the address space is essentially irrelevant.

The working set model has nice implications for scheduling. When a process is scheduled, or, in the case of Win32, when a thread is scheduled within a process, the scheduler makes sure that all of the pages of the current working set are preloaded before actually making the thread feasible. This minimizes the number of page faults that are actually taken, and improves overall system throughput.

When you have a very large executable, it is possible that function A calling function B calling function C calling...etc. will generate a large working set of code. You can get certain performance improvements by causing closely-associated routines to be linked so they fall within a small set of pages. The Microsoft linker provides no such capability. When I first did this, we were working on a timeshared machine which had a whopping 750K of RAM (well, magnetic core in those days). That is, a machine shared among 60 people with the computational horsepower and physical memory of a 8088 PC. In those days, we actually saw performance improvements from what we called "adjacency linking". It takes a lot of effort, and in modern systems it just doesn't seem to matter very much. The last time I worried about this was in 1969 or 1970.

Where it matters

Where address space matters is in the size of your data. And not just the data that is in use; the total size of your heap is what matters. Where the code size has virtually no impact on your overall performance, your data space can kill your performance and/or your reliability. When you want to expend effort understanding your program size, understand your data structures. As the data structures increase, and the heap fragments, or you leak memory, you end up with an ever-growing memory footprint. This starts to be serious, because this can, without much effort on your part, exceed the size of your code working set by two orders of magnitude.

Paging performance will kill you when the algorithm otherwise looks sane. Consider an application that is doing a convolution algorithm on a large bitmap. If it is set up to process the data along one axis, it will access all the pages successively, meaning that you will maximize the number of page faults. If, however, you arrange the data so that you can process all of one page of data before moving to the next page, you will minimize page faults and get at least an order of magnitude improvement, if not two orders of magnitude. If you can further take advantage of the L1/L2 caching, you might get another order of magnitude improvement.

Heap fragmentation is the major cause of large data working set. This is caused by doing allocations and releases in a pattern that leaves holes in the heap that are no longer usable. This is why MFC always allocates CString values in multiples of 256 bytes; this minimizes the sort of fragmentation that precise allocation causes, making it possible to reuse previous string values left free on the heap. But if you're not careful, you can still fragment the heap badly. When you are programming in C, you should do mallocs only of certain quantum sizes, for example, 256 bytes, and/or use user-defined heaps (HeapCreate and support functions). It is fairly easy to do this in C++ if you are defining your own classes, but much harder to do if you are using MFC.

But I Free The Space!

A common misconception is that these numbers tell something about the memory your program is consuming. As I point out, this is not what those numbers tell you. They tell you the amount of address space that is in use. This has nothing to do with the amount of that address space your program is actually using.

For example, when you allocate a block of storage, the allocator may go to the kernel to get more address space. It then fills that address space in with some headers and other information. As you allocate the storage, it partitions that address space into smaller blocks. I have never looked into when address space is actually released back to the kernel, but it doesn't happen very often.

In addition, say you allocated 20,000 elements of 100 bytes each. You will see your address space grow by 2,000,000 bytes. Then you release the storage and your address space doesn't shrink. Did you fail to release it? Is there a bug? 

No. You probably released it, and it is not a bug that you are seeing. What you now have are 20,000 little free blocks of 100 bytes each on the free list. They are still in the address space, but they are available for subsequent allocation. Actually, many of these will be merged into larger blocks, but the address space remains allocated. Consequently, the numbers you see in task manager are almost but not quite completely useless for telling how much of your memory is in use. If you see it shrink, this is Good, but if you don't, it is not necessarily Bad.

So you really can't trust these numbers. When I care, I use the heap walking functions to track what is going on.

At some point, I'm going to do something about a tool for studying heap fragmentation. MFC provides some nice tools for this. But for now, all I can suggest is exercising due diligence in handling allocation. Use a tool like Bounds Checker for Windows (from NuMega) to verify you are not leaking memory and other resources.

What about those "Optimizer" programs

Any number of examples of what has been called "fraudware" have begun to emerge.  These are products that claim to optimize your computer's performance by minimizing applications.  The reason they are called "fraudware" is because they actually don't improve the performance of your computer.  They just make Task Manager numbers look pretty.  This is not at all the same.  What they do is force the working set of other applications to be very small, which actually increases their paging traffic, thus actually decreasing your system performance.  These fraudware packages are based on the principle that if you haven't a clue what the Task Manager numbers mean, then making them look good makes you think that Something Good has actually happened.  Another way they work is by forcing their own working set to be massive, forcing all other apps out, then decreasing their own working set size down.  By this time, the damage is done; everything is paged out, and you misinterpret the small numbers as being Good Numbers, when in fact they are Bad Numbers. These programs cannot possibly work, and any claims they make about what they do are almost certainly fraudulent. Do not waste your money on them.

[Dividing Line Image]

The views expressed in these essays are those of the author, and in no way represent, nor are they endorsed by, Microsoft.

Send mail to newcomer@flounder.com with questions or comments about this web site.
Copyright © 2000-2011, The Joseph M. Newcomer Co. /FlounderCraft Ltd. All Rights Reserved.
Last modified: June 17, 2011