flipCode - Tech File - Toby Jones [an error occurred while processing this directive]
Toby Jones
Click the name for some bio info

E-Mail: tjones@hot-shot.com



   01/23/2001, Why I Trust My Compiler


I don't think it has been announced yet, so I won't mention any names. A friend of mine recently got hired by a prominent local game company to do Playstation 2 work. Not bad for a guy 6 months out of university. He worked with me at Sonic Foundry for a few months, where he polished his technique writing our consumer video editor, Video Factory. On his off time he worked on his 3D code. He worked very hard and achieved his goal. Good job man! Keep in mind that this guy was writing games in his spare time for the last 3 years. Live it, breathe it, and eat it.

About two years ago, I wanted to learn how to write Windows assembly language. I'm not sure why, but I'm sure part of it had to do with seeing how small a Windows program could be. After a little bit of research and a couple of afternoons later, I had a Windows program completely written in assembly: a recreation of the classic Hello, Win32 program. After a bit of tweaking, I was able to get the size down to 1.5k, which is probably about as small as you can reasonably expect. I'm sure I could squeeze a few more bytes here and there, but the project was designed to see what kind of large-scale improvements could be made.

A guy I know sent me a white paper (http://www.seas.gwu.edu/~adagroup/sigada-website/lawlis.html) a few years ago, which I had completely forgotten about until recently. The paper described how a programming house had used Ada to get better size and speed than assembly. Ada has long been a favorite of embedded programming, where size is of primary concern. Knowing that Ada and C are fairly similar, I finally decided to see if I could get comparable results with my C compiler.

Just so I knew exactly what I was comparing, I checked my notes to see how I built the assembly version. I assembled with the open source assembler, NASM, and linked with the Microsoft linker. The initial version was 16k. I took a look at the EXE with a hex editor, and noticed that much of the program actually consisted of zeros. So I used the services of Collake Software's PECompact. You may be aware of PECompact's compression abilities, but what you might not know is that PECompact originally was an EXE trimmer, and still retains that ability. This way, I could trim the fat of the EXE without actually adding any sort of compression. Since PECompact is a single pass trimmer, I applied it twice and came up with 6k then 3.5k. Finally, I added linker switches to reduce the size of the stack and heap. I ended up with 1.5k.

I started a new workspace in Visual C++ and wrote up a quick Hello, Win32 program in C. Using the default options, I compiled the program to 170k. Now, it looked like I had some work to do if I wanted to match the 1.5k of my assembly program.

The default compiler settings are rarely the optimal ones. The first major optimization I used was turning off debug code and switching to a release build. This was by far the largest improvement, which brought it down to 36k. It is no secret that debug code will add a lot of bulk to an EXE.

Next, I got rid of the C library by turning on the "Ignore all default libraries" checkbox in the Project Settings->Link dialog. Unfortunately, after setting that, I got a linker error:

LINK : error LNK2001: unresolved external symbol _WinMainCRTStartup

Being familiar with the C runtime library, I recognized this as the entry point to the Windows application. This is the function that calls WinMain. Since I wasn't linking with the C runtime, I wrote a custom replacement:

void WinMainCRTStartup()
{
    STARTUPINFO info;
    info.dwFlags = 0;
    GetStartupInfo(&info);

    int iCmdShow = info.dwFlags & STARTF_USESHOWWINDOW ?
                                  info.wShowWindow :
                                  SW_SHOWDEFAULT;

    int x = WinMain(GetModuleHandle(NULL),
                    NULL,
                    NULL,
                    iCmdShow);
    ExitProcess(x);
}
This code brought my EXE size down to 16k. That was good news because that was the initial size of the assembly version. Applying PECompact's trimmer to the program twice brought the size to 6k then 3.5k.

As a final optimization, I switched the compiler optimization settings from "Maximize Speed" to "Minimize Size." This brought me down to 3k. Not finding a way to change the heap settings from the IDE, I decided to stop here.

It is not usually wise to shrink the default heap size. Windows uses the heap extensively, and if there is not enough space, your program will crash. This might not be much of an issue for these programs here, but they are very important in real commercial apps.

With this knowledge in tow, I did, in fact, beat assembly language with C. The assembly version was 3.5k and the C version was 3k. The assembly version did have a few additions, such as an extra string and structured exception handling. But even these additions wouldn't amount to 500 bytes. The assembly version even combined WinMain and WinMainCRTStartup.

The most important improvements were made by removing the C runtime library and by using PECompact to trim the EXE. Removing the C runtime is not as big a barrier as it would first seem, as Windows provides much of the equivalent functionality. PECompact merely strips unnecessary info from an EXE, and doesn't have any drawbacks. In other words, you all should be using this program, if only for its trimming abilities.

Have you ever seen a 3k Windows program? Certainly, you haven't seen one written in C. Steve Gibson (http://www.grc.com) can do this with his all-assembly approach, but this just shows that even C can do the trick if you are willing to tweak it a bit. These were fairly simple examples. I'd be interested in hearing people's results with larger programs. Email me at tjones@hot-shot.com.

I dug up a few links that might help you:
  • http://msdn.microsoft.com/msdnmag/issues/01/01/hood/hood0101.asp
  • http://users.lia.net/chris/win32/crt.html
  • The first link is a recent article by Matt Pietrek. He discusses removing the C runtime and replacing it with a custom version. This is a definite read for anyone who wants to extend my memory tracker (presented in my previous article) to work with memory allocated by global C++ objects.

    The second link talks about how you can live without the C runtime library.

    Assembly doesn't need to have a size advantage over C. I hope I've shown how you can avoid code bloat. However, C will _never_ have the speed advantages of assembly. There are just too many algorithmic tricks you can play with assembly that you can't do with C.

    -Toby





  • 01/23/2001 - Why I Trust My Compiler
  • 12/06/2000 - Latest News And A Simple Memory Tracker
  • 09/21/2000 - Introduction

  • This document may not be reproduced in any way without explicit permission from the author and flipCode. All Rights Reserved. Best viewed at a high resolution. The views expressed in this document are the views of the author and NOT neccesarily of anyone else associated with flipCode.

    [an error occurred while processing this directive]