Unit 4
Unit 4
Unit 4
CODE OPTIMIZATION
Optimization
Principles of source optimization:
Optimization is a program transformation technique, which tries to improve the code
by making it consume less resource (i.e. CPU, Memory) and deliver high speed. In
optimization, high-level general programming constructs are replaced by
very efficient low-level programming codes.
A code optimizing process must follow the three rules given below:
• The output code must not, in any way, change the meaning of the program.
• Optimization should increase the speed of the program and if possible, the program
should demand less number of resources.
• Optimization should itself be fast and should not delay the overall compiling
process. Efforts for an optimized code can be made at various levels of compiling the
process.
• At the beginning, users can change/rearrange the code or use better algorithms to write
the code.
• After generating intermediate code, the compiler can modify the intermediate code by
address calculations and improving loops.
• While producing the target machine code, the compiler can make use of memory
hierarchy and CPU registers.
Types of optimization:
Optimization can be categorized broadly into two types: machine independent and
machine dependent.
Machine-independent Optimization:
In this optimization, the compiler takes in the intermediate code and transforms a part
of the code that does not involve any CPU registers and/or absolute memory locations. For
example:
do
{
item = 10;
value = value + item;
} while(value<100);
This code involves repeated assignment of the identifier item, which if we put this way:
Item = 10;
do
{
value = value + item;
} while(value<100);
should not only save the CPU cycles, but can be used on any processor.
Machine-dependent Optimization
Machine-dependent optimization is done after the target code has been generated and
when the code is transformed according to the target machine architecture. It involves CPU
registers and may have absolute memory references rather than relative references. Machine-
dependent optimizers put efforts to take maximum advantage of memory hierarchy.
Steps before optimization:
1) Source program should be converted to Intermediate code
2) Basic blocks construction
3) Generating flow graph
4) Apply optimization
Basic Block
Source codes generally have a number of instructions, which are always executed in
sequence and are considered as the basic blocks of the code. These basic blocks do not have
any jump statements among them, i.e., when the first instruction is executed, all the
instructions in the same basic block will be executed in their sequence of appearance without
losing the flow control of the program.
A program can have various constructs as basic blocks, like IF-THEN-ELSE, SWITCH-
CASE conditional statements and loops such as DO-WHILE, FOR, and REPEAT-UNTIL,
etc.
We may use the following algorithm to find the basic blocks in a program:
1) Search header statements of all the basic blocks from where a basic block starts. Following
specifications denotes the header statement:
O First statement of a program.
O Statements that are target of any branch (conditional/unconditional).
O Statements that follow any branch statement.
2) Header statements and the statements following them form a basic block.
3) A basic block does not include any header statement of any other basic block.
Basic blocks are important concepts from both code generation and optimization point of
view.
Basic blocks play an important role in identifying variables, which are being used
more than once in a single basic block. If any variable is being used more than once, the
register memory allocated to that variable need not be emptied unless the block finishes
execution.
Conversion from basic block to flow graph
Flow Graph
Basic blocks in a program can be represented by means of control flow graphs. A
control flow graph depicts how the program control is being passed among the blocks. It is a
useful tool that helps in optimization by help locating any unwanted loops in the program.
Loop optimization & its types
Most programs run as a loop in the system. It becomes necessary to optimize the loops
in order to save CPU cycles and memory. Loops can be optimized by the following
techniques:
• Invariant code :
O If a computation produces the same value in every loop iteration, move it
out of the loop.
O An expression can be moved out of the loop if all its operands are invariant in
The loop
O Constants are loop invariants.
• Induction analysis: A variable is called an induction variable if its value is altered
within the loop by a loop-invariant value.
Eg:
extern int sum;
int foo(int n)
{
int i, j;
j = 5;
for (i = 0; i < n; ++i) {
j += 2;
sum += j;
}
return sum;
}
This can be re written as, extern int sum;
int foo(int n)
{
int i;
sum = 5;
for (i = 0; i < n;
++i)
{
sum += 2 * (i + 1);
}
return sum;
}
• Reduction in Strength: There are expressions that consume more CPU cycles, time, and
memory. These expressions should be replaced with cheaper expressions without
compromising the output of expression. For example, multiplication (x * 2) is
expensive in terms of CPU cycles than (x << 1) and yields the same result.
c = 7;
for (i = 0; i < N; i++)
{
y[i] = c * i;
}
can be replaced with successive weaker additions
c = 7;
k = 0;
for (i = 0; i < N; i++)
{
y[i] = k;
k = k + c;
}
• Constant folding and constant propagation
Constant folding: It is the process of recognizing and evaluating statements with
constant expressions (i=22+222+2222 to i=2466), string concatenation (“abc”+”def” to
“abcdef”) and expressions with arithmetic identities (x=0; z=x*y[i]+x*2 to x=0; z=0;)
at compile time rather than at execution time.
Constant propagation:
It is the process of substituting values of known constants in an expression at compile
time.
Eg:int x=14; int y=7+x/2;
return
y*(28/x+2);
Applying constant folding and constant
propagation, int x=14;
int y=14;
return 56;
Direct Acyclic Graph (DAG):
DAG is a tool that depicts the structure of basic blocks, helps to see the flow of values
flowing among the basic blocks, and offers optimization too. DAG provides easy
transformation on basic blocks. DAG can be understood here:
• Leaf nodes represent identifiers, names or constants.
• Interior nodes represent operators.
• Interior nodes also represent the results of expressions or the identifiers/name where
the values are to be stored or assigned.
Example:
t0 = a + b
t1 = t0 + c
d = t0 + t1
Data flow analysis:
In order to find out the value of any variable (A) of a program at any point (P) of the program,
Global data flow analysis is required.
To process global data flow analysis on a basic block (B), we need to perform following
functions:
(i) KILL(B)
(ii) GEN(B)
(iii) IN(B)
(iv) OUT(B)
GEN(B) : Set of definitions generated inside the block B
KILL(B) : Set of definitions outside the block B, which also has definition inside B
IN(B) : Set of definitions taken as an input to block B on a call to B
OUT(B) : Set of definitions outputted from B
Sample Problem:
Solution:
i) Generate GEN(B) and KILL(B) for all blocks:
KILL(B1) = {0 0 1
1 1} KILL(B2) =
{1 1 0 1 1}
KILL(B3) = {1 1 1
0 0} KILL(B4) =
{1 1 1 1 0}
KILL(B5) = {1 1 1
1 1}
ii) Process algorithm to compute IN(B) and
OUT(B) Iteration 1:
IN(B) = NULL OUT(B) = GEN(B)
B1 00000 11000
B2 00000 00100
B3 00000 00011
B4 00000 00001
B5 00000 00000
Change = TRUE
Iteration 2:
Change = FALSE
IN(B1) = {0 0 0 0 0}
Change = TRUE
OUT(B1) = (0 0 1 0 0 – 0 0 1 1 1) U (1 1 0 0 0)
= (0 0 1 0 0) & (1 1 0 0 0) | (1 1 0 0 0)
OUT(B1) = (1 1 0 0 0)
Proceeding the algorithm further,
IN(B) = Vout(P) OUT(B) = [IN(B) –
KILL(B)] U GEN(B)
B1 00000 11000
B2 1 1 0 0 0` 01100
B3 01100 00110
B4 00110 00101
B5 00111 00111
Iteration 3:
B1 01100 11000
B2 11111 01111
B3 01111 00110
B4 00110 00101
B5 00111 00111
Iteration 4:
B1 01111 11000
B2 11111 01111
B3 01111 00110
B4 00110 00101
B5 00111 00111
Iteration 5:
B1 01111 11000
B2 11111 01111
B3 01111 00110
B4 00110 00101
B5 00111 00111
As iteration 4 and 5 produces same values, the process ends. Hence the value of definition
anywhere at any point of time is deduced.
Dominators
In order to deduct the control flow within basic blocks, it’s required to compute dominators.
In the flow graph consisting a node D and E, if path leaves from D to E, then node D is said
as dominating E.
Dominator Tree:
Dominator tree represents the hierarchy of the blocks and its execution flow. Here, the initial
node is taken as the root and every further parent is said to be intermediate dominator of child
node.
If ( debug ) {
Print debugging information
}
In the intermediate representations the if-statement may be translated as:
One obvious peephole optimization is to eliminate jumps over jumps .Thus no matter what
the value of debug; (a) can be replaced by:
If debug ≠1 goto L2
Print debugging information
L2: …………………………… (b)
If debug ≠0 goto L2
Print debugging information
L2: …………………………… (c)
As the argument of the statement of (c) evaluates to a constant true it can be replaced
By goto L2. Then all the statement that print debugging aids are manifestly
unreachable and can be eliminated one at a time.
goto L1
….
If there are now no jumps to L1, then it may be possible to eliminate the statement
L1:goto L2 provided it is preceded by an unconditional jump .Similarly, the sequence
if a < b goto L1
….
L1: goto L2 (e)
can be replaced by
If a < b goto L2
….
L1: goto L2
goto L1
If a < b goto L2
goto L3
…….
L3:
While the number of instructions in(e) and (f) is the same, we sometimes skip the
unconditional jump in (f), but never in (e).Thus (f) is superior to (e) in execution time
X2 → X*X
i:=i+1 → i++
i:=i-1 → i- -