Home | | Multicore Application Programming For Windows, Linux, and Oracle Solaris | | Multi - Core Architectures and Programming | Performance and Convenience Trade-Offs in Source Code and Build Structures

Chapter: Multicore Application Programming For Windows, Linux, and Oracle Solaris - Coding for Performance

| Study Material, Lecturing Notes, Assignment, Reference, Wiki description explanation, brief detail |

Performance and Convenience Trade-Offs in Source Code and Build Structures

The structure of the source code for an application can cause differences to its perform-ance. Source code is often distributed across source files for the convenience of the developers.

Performance and Convenience Trade-Offs in Source Code and Build Structures

 

The structure of the source code for an application can cause differences to its perform-ance. Source code is often distributed across source files for the convenience of the developers. It is appropriate that the developers’ convenience is one of the main criteria for structuring the sources, but care needs to be taken that it does not cause inconven-ience to the user of an application.

 

Performance opportunities are lost when the compiler sees only a single file at a time. The single file may not present the compiler with all the opportunities for optimizations that it might have had if it were to see more of the source code. This kind of limitation is visible when a program uses an accessor function—a short function that returns the value of some variable. A trivial optimization is for the compiler to replace this function call with a direct load of the value of the variable. Consider the code sequence shown in Listing 2.5 for an example of accessor functions.

 

Listing 2.5     Accessor Functions

#include <stdio.h>

 

int a;

 

void setvalue( int v ) { a = v; }

 

int    getvalue() { return a; }

 

void main()

 

{

 

setvalue( 3 );

printf( "The value of a is %i\n", getvalue() );

 

}

 

The code in Listing 2.5 can be replaced with the equivalent but faster code shown in Listing 2.6. This is an example of inlining within a source file. The calls to the routines getvalue() and setvalue() are replaced by the actual code from the functions.

 

Listing 2.6     Pseudosource Code After Inlining Optimization

#include <stdio.h>

 

int a;

 

void main()

 

{

 

a = 3;

printf( "The value of a is %i\n", a );

 

}

At some optimization level, most compilers support inlining within the same source file. Hence, the transformation in Listing 2.6 is relatively straightforward for the compiler to perform. The problem is when the functions are distributed across multiple source files. Fortunately, most compilers support cross-file optimization where they take all the source code and examine whether it is possible to further improve performance by inlining a routine from one source file into the place where it’s called in another source file. This can negate much of any performance loss incurred by the structure used to store the source code. Cross-file optimization will be discussed in more detail in the sec-tion “How Cross-File Optimization Can Be Used to Improve Performance.”

 

Some build methodologies reduce the ability of the compiler to perform optimizations across source files. One common approach to building is to use either static or archive libraries as part of the build process. These libraries combine a number of object files into a single library, and at link time, the linker extracts the relevant code from the library.

 

Listing 2.7 shows the steps in this process. In this case, two source files are combined into a single archive, and that archive is used to produce the application.

 

Listing 2.7     Creating an Archive Library

$ cc -c a.c

 

$ cc -c b.c

 

$ ar -r lib.a a.o b.o

 

ar: creating lib.a

 

$ cc main.c lib.a

 

There are three common reasons for using static libraries as part of the build process:

 

n   For “aesthetic” purposes, in that the final linking of the application requires fewer objects. The build process appears to be cleaner because many individual object files are combined into static libraries, and the smaller set appears on the link line. The libraries might also represent bundles of functionality provided to the executable.

 

n   To produce a similar build process whether the application is built to use static or dynamic libraries. Each library can be provided as either a static or a dynamic ver-sion, and it is up to the developer to decide which they will use. This is common when the library is distributed as a product for developers to use.

 

n   To hide build issues, but this is the least satisfactory reason. For example, an archive library can contain multiple versions of the same routine. At link time, the linker will extract the first version of this routine that it encounters, but it will not warn that there are multiple versions present. If the same code was linked using individ-ual object files without having first combined the object files into an archive, then the linker would fail to link the executable.

 

Listing 2.8 demonstrates how using static libraries can hide problems with multiply defined functions. The source files a.c and b.c both contain a function status(). When they are combined into an archive and linked into an executable, the linker will extract one of the two definitions of the function. In the example, the linker selects the definition from a.c. However, the build fails with a multiply defined symbol error if an attempt is made to directly link the object files into an executable.

Listing 2.8  Example of a Static Library Hiding Build Issues

$ more a.c

 

#include <stdio.h> void status()

{

 

printf("In status of A\n");

 

}

 

$ more b.c

 

#include <stdio.h> void status()

{

 

printf("In status of B\n");

 

}

 

$ cc -c a.c $ cc -c b.c

 

$ ar -r lib.a a.o b.o ar: creating lib.a

 

$ more main.c void status(); void main()

{

status();

 

}

 

$ cc main.c lib.a $ a.out

 

In status of A

 

$ cc main.c a.o b.o

 

ld: fatal: symbol 'status' is multiply-defined: (file a.o type=FUNC; file b.o type=FUNC);

ld: fatal: File processing errors. No output written to a.out

 

An unfortunate side effect of using static libraries is that some compilers are unable to perform cross-file optimization of code contained in the static libraries. This may mean that functions are not inlined from the static library into the executable or that the code held in the static library does not take any part in cross-file optimization.

 

Study Material, Lecturing Notes, Assignment, Reference, Wiki description explanation, brief detail


Copyright © 2018-2020 BrainKart.com; All Rights Reserved. Developed by Therithal info, Chennai.