Download Optimizing software in C++
Transcript
The calculation of this polynomial can be done with just two additions by the use of two
induction variables:
// Example 7.23b. Calculate polynomial with induction variables
const double A = 1.1, B = 2.2, C = 3.3; // Polynomial coefficients
double Table[100];
// Table
int x;
// Loop counter
const double A2 = A + A;
// = 2*A
double Y = C;
// = A*x*x + B*x + C
double Z = A + B;
// = Delta Y
for (x = 0; x < 100; x++) {
Table[x] = Y;
// Store result
Y += Z;
// Update induction variable Y
Z += A2;
// Update induction variable Z
}
The loop in example 7.23b has two loop-carried dependency chains, namely the two
induction variables Y and Z. Each dependency chain has a latency which is the same as the
latency of a floating point addition. This is small enough to justify the method. A longer loopcarried dependency chain would make the induction variable method unfavorable, unless
the value is calculated from a value that is two or more iterations back.
The method of induction variables can also be vectorized if you take into account that each
value is calculated from the value that lies r places back in the sequence, where r is the
number of elements in a vector or the loop unroll factor. A little math is required for finding
the right formula in each case.
Inlined functions have a non-inlined copy
Function inlining has the complication that the same function may be called from another
module. The compiler has to make a non-inlined copy of the inlined function for the sake of
the possibility that the function is also called from another module. This non-inlined copy is
dead code if no other modules call the function. This fragmentation of the code makes
caching less efficient.
There are various ways around this problem. If a function is not referenced from any other
module then add the keyword static to the function definition. This tells the compiler that
the function cannot be called from any other module. The static declaration makes it
easier for the compiler to evaluate whether it is optimal to inline the function, and it prevents
the compiler from making an unused copy of an inlined function. The static keyword also
makes various other optimizations possible because the compiler doesn't have to obey any
specific calling conventions for functions that are not accessible from other modules. You
may add the static keyword to all local non-member functions.
Unfortunately, this method doesn't work for class member functions because the static
keyword has a different meaning for member functions. You can force a member function to
be inlined by declaring the function body inside the class definition. This will prevent the
compiler from making a non-inlined copy of the function, but it has the disadvantage that the
function is always inlined even when it is not optimal to do so (i.e. if the member function is
big and is called from many different places).
Some compilers have an option (Windows: /Gy, Linux: -ffunction-sections) which
allows the linker to remove unreferenced functions. It is recommended to turn on this option.
7.4 Obstacles to optimization by CPU
Modern CPU's can do a lot of optimization by executing instructions out of order. Long
dependency chains in the code prevent the CPU from doing out-of-order execution, as
explained on page 22.
74