SPO600 – Lab 4 -Compiled C Lab

In this lab, we were tasked to with testing out different compiler options on a simple hello world C program and seeing how these different options optimize our code, as well as the accompanying changes to performance and file size.

The initial baseline run was set to use the following code and compiler options:

Hello.c

#include  int main() { printf("Hello World!\n"); }

gcc compiler

-g               # enable debugging information
-O0              # do not optimize (that's a capital letter and then the digit zero)
-fno-builtin     # do not use builtin function optimizations

Using the “objdump –source” command, we are able to view the assembly language of the created executable. Below is a snippet of the main function of the source code along with the disassembly:

int main() {
 4004f6: 55                  push %rbp
 4004f7: 48 89 e5            mov %rsp,%rbp
 printf("Hello World!\n");
 4004fa: bf a0 05 40 00      mov $0x4005a0,%edi
 4004ff: b8 00 00 00 00      mov $0x0,%eax
 400504: e8 e7 fe ff ff      callq 4003f0 <printf@plt>
 400509: b8 00 00 00 00      mov $0x0,%eax
}
 40050e: 5d pop %rbp

Baseline size of the executable was approximately 11000 bytes.

Compiler Options:

1. Add -static option

The easiest thing to note is the dramatic increase in size of the executable. It was increased by about 1000 times the original size. The reason being is that it loads all of the libraries for the C code at compile time without the use of a link table. Additional headers were also added to the .plt section. Theoretically, the program should run faster as the function call for printing would be directly inside the program rather than the linker table.

2. Remove the compiler option -fno-builtin

Removing this option tells the compiler to go ahead and use built in function optimizations. The main change that was resulted by this was that the compiler optimized the printf call to a puts. The reason being is that any simple cases of printing strings, the compiler defaults to using puts than printf. The size of this executable is slightly smaller than when you include the option.

3. Remove the compiler option -g 

Removing this option tells the compiler to disable debugging information that is included with the code, thereby reducing the size of the executable. In the disassembly output, code which would normally tell you line numbers for compile errors are removed. Furthermore, without this option, you will no longer be able to view the source code with the disassembly when using objdump –source.

4. Add additional arguments to the printf() function in your program. 

In order to properly see the changes in disassembly, 10 arguments were added sequentially to the printf() function. When the printf() function was only given one argument, it was converted to puts. Upon adding two or more arguments, puts was no longer used and the compiler used printf. With each additional argument, a new register was used. This occurred until the number of arguments reached 6 or more, in which a stack was created and pushq was used to push the new arguments to it.

5. Move the printf() call to a separate function named output(), and call that function from main()

Adding this function to the code causes a slight change in which the main function now calls the output function and the output disassembly now has the necessary lines for print the string.

6. Remove -O0 and add -O3 to the gcc options. Note and explain the difference in the compiled code.

There are several levels to optimization with the gcc compiler. When using -O0, the compiler actually doesn’t do any optimization. As you increases the number from 0 all the way to 3, more and more optimizations are performed by the compiler. So when you utilize the -O3 option while keeping the output function from above, the compiler simply removes the function altogether and moves the printing back to main, in a process called function inlining. Because the -O3 option allows the compiler to be very liberal with its code changes, the compilation time will also increase as a tradeoff for increased performance.

Conclusions

These are just a few of the possible compiler options that can be done to optimize your code. Its clear that there a lot of things being done underneath the hood during compile time and its important to have an understanding of these things when you using these options, because as you can see, depending on your options, the compiler can be very liberal with its changes.

 

Advertisements

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s