170 likes | 279 Vues
This report from Stanford University details crucial code optimization techniques pioneered by Fran Allen in 1968, who later won the Turing Award in 2006. The document covers key concepts such as flow graphs, constant folding, global common subexpressions, induction variables, and loop-invariant code motion. It analyzes intermediate code, demonstrating how to expose optimizable constructs and simplify code through techniques like basic block formation and strength reduction. Through illustrative examples, the report provides insights into effective principles for enhancing code efficiency.
E N D
Code Optimization Circa 1969 Flow GraphsConstant FoldingGlobal Common SubexpressionsInduction Variables/Reduction in Strength Jeffrey D. Ullman Stanford University
Dawn of Code Optimization • A never-published Stanford technical report by Fran Allen in 1968. • Fran won the Turing award in 2006. • Flow graphs of intermediate code. • Key things worth doing.
Intermediate Code • Steps with < 3 addresses (2 operands + result). • Assignments with < 1 arithmetic operator. • Examples: x = 0; x = y; x = y+z but not x = w+y*z. • Indirection and pointer access. • Examples: x = *p; p = &x; x = a[i]; x[i] = y. • Branches with one comparison operator, gotos. • Examples: if x == 0 goto s1; goto s2 but not if x == 0 || y == 1 goto s3. • Call, return. • Arguments copied like assignments w/o operator.
Example: Intermediate Code • Here’s typical source code: for (i=0; i<n; i++) A[i] = 1.0; • Intermediate code exposes optimizable constructs we cannot see at source-code level. i = 0 L1: if i>n goto L2 t1 = 8*i A[t1] = 1.0 i = i+1 goto L1 L2: ... Notice hidden offset calculation.
Basic Blocks • Make flow explicit by breaking into basic blocks= sequences of steps with entry at beginning, exit at end, no branches in interior. • Break intermediate code at leaders = • First statement. • Statements targeted by a branch or goto. • Statements following a branch. • Simplification: make each intermediate-code statement its own basic block. • Saves the trouble of figuring out data flow within blocks.
i = 0 if i>n goto … t1 = 8*i A[t1] = 1.0 i = i+1 Example: Basic Blocks i = 0 L1: if i>n goto L2 t1 = 8*i A[t1] = 1.0 i = i+1 goto L1 L2: ...
Induction Variables • x is an induction variableat a point within a loop if it takes on a linear sequence of values each time through that point. • One induction variable can do the job of another. • Common case: loop index like i and computed array offset like t1. • We really don’t need i. • Replace multiplication by addition (reduction in strength).
Example: Replace i by t1 • In the basic block t1 = 8*i; A[t1] = 1.0; i = i+1, t1 = 8i at the point where t1 is used, at A[t1] = 1.0. • Replace i = i+1 by t1 = t1 + 8. • Initialize t1 = 0. • Now, t1 always has value 8i where it is used, even though its calculation does not depend on i. • We can drop i = 0 and t1 = 8*i altogether if we replace the test i <= n.
i = 0 if i>n goto … t1 = 8*i A[t1] = 1.0 i = i+1 Example: Eliminating i Needed to express condition i <= n in terms of t1 t1 = 0 n1 = 8*n if t1>n1 goto … A[t1] = 1.0 t1 = t1+8 Question: is that always good? Initialization block grows, but the loop block shrinks
Loop-Invariant Code Motion • Sometimes, a computation is done each time around a loop. • Move it before the loop to save n-1 computations. • Be careful: could n=0? I.e., the loop is typically executed 0 times.
Example: y+z is Loop-Invariant So move computation of t1 here and compute it only once i = 0 i = 0 t1 = y+z if i>n goto … if i>n goto … t1 = y+z x = x+t1 i = i+1 x = x+t1 i = i+1 Neither y nor z changes within the loop
Constant Folding • Sometimes a variable has a known constant value at a point. • If so, replacing the variable by the constant simplifies and speeds-up the code. • Easy within a basic block; harder across blocks.
i = 0 n = 100 if i>n goto … t1 = 8*i A[t1] = 1.0 i = i+1 Example: First Flowgraph, but Fixed n Notice n1 is no longer needed So make the substitution at compile time Only possible assignment to this n1 is n1 = 800 t1 = 0 n1 = 8*100 t1 = 0 if t1>n1 goto … if t1>800 goto … A[t1] = 1.0 t1 = t1+8 A[t1] = 1.0 t1 = t1+8 Not only did we eliminate n1, but comparison of a variable And a constant can’t be worse than comparing two variables.
Global Common Subexpressions • Suppose block B has a computation of x+y. • Suppose we are certain that when we reach this computation, we have: • Computed x+y, and • Not subsequently reassigned x or y. • Then we can hold the value of x+yin a temporary variable and use it in B.
a = x+y t = x+y a = t b = x+y t = x+y b = t c = x+y c = t Example: Common Subexpression Elimination t holds value of x+y Every place x+y might be computed prior to c = x+y We know t has the right value here
Partial Redundancy Elimination • Not known in 1969. • Sometimes, an expression has been computed along some paths to a block B, but not along others. • Replicate the block so the expression is computed exactly when it is needed.
t = x+y a = t t = x+y b = t c = t Example: Partial Redundancy Elimination We may already have computed x+y So duplicate the block, depending on how it is reached t = x+y a = t b = t t = x+y b = t c = t Here, t already holds the value we need for b Here it doesn’t; so compute it