Open In App

Partial-Redundancy Elimination

Last Updated : 01 Nov, 2022
Improve
Improve
Like Article
Like
Save
Share
Report

Code optimization is one of the phases of compilation. This phase is often known as Machine-Independent Code Optimization. In this phase, several techniques and procedures are implied to reduce the code’s runtime. The code that is being optimized here is also of different types, such as dead code, redundant code, partially redundant code, etc. 

In this article, we will discuss redundancy in code. A redundant piece of code contains expressions or statements that repeat themselves or produce the same results throughout the execution flow of the code. Similarly, a partially redundant code has redundancy in one or more execution flows of the code but not necessarily in all the paths of execution. 

These redundancies may exist in various forms, such as in common sub-expressions and loop-invariant expressions, etc.

For example, consider figure 1a, here the expression “b / c” is evaluated twice along one flow path, i.e., when the condition b > c is true even though there is no change in the values of variables b and c. Hence, this particular piece of code is partially redundant. This redundancy can be eliminated if we compute the expression “b / c” once and store it in a variable, say t. Then we can use the variable t in our code whenever we need the value of the expression “b / c“.

Partial redundant Code

Example of Partial redundant Code

Can we eliminate all redundancies? 

In the earlier example, we eliminated the redundancy in the code by duplicating the common expression and moving it to blocks of other paths of the code. However, it is not always possible to implement this technique in codes with tens or hundreds of execution paths. For example, in figure 2 below, the expression b / c is redundant in the path “1 ⇒ 2 ⇒ 3”. But we cannot duplicate and move the expression to block 3, as it was done in the previous example, because doing so here would create an unnecessary computation in the paths other than “1 ⇒ 2 ⇒ 3” or “1 ⇒ 3 ⇒ 4”.

Figure 1

Although this issue can be solved if it is allowed to create new blocks in the execution path of the code as in figure 3. However, this technique is also inadequate to eliminate all the redundancies in the code because we might need to duplicate the expressions to separate the paths where redundancy occurs.

Figure 2

Thus, we can only eliminate the common expressions that do not generate any duplication in code.

The Lazy-Code-Motion Algorithm

After a code is optimized for eliminating redundancy, they are expected to have the following characteristics: 

  1. All the common expressions that do not generate any duplication in code are eliminated.
  2. The optimized code has not introduced any new computations to the previous code.
  3. The computation of expressions is done at the latest possible time.

The Lazy code motion is the optimization of partial redundancy to perform the computations at the latest possible time in the code. This property is important as the common expression’s values are stored in registers until their last use, hence, the latest computation of these expressions results in their smaller lifetime.

The intuition of Partial Redundancy of a single expression:

Consider an expression E which is redundant in block A, which means that E has been computed in all the execution paths that reach block A. Now, in this case, there must exist a set of blocks, say B, among which all blocks have the expression E, hence making it redundant in block A. The set of outgoing edges from B in the flowgraph necessarily forms a cut-set that can disconnect block A from the entry of the code, if removed.

In case of partial redundancy, the lazy code-motion algorithm tries to make copies of the expression E in the other execution paths and make it fully redundant. If optimization is successful, the optimized code’s flowgraph will also contain the cut-set between block A and the entry.

Anticipation of Expressions:

To ensure there are no extra computations in the code, copies of partial redundant code should be inserted at points where the expression is anticipated. An expression E  is said to be anticipated at a point P if the values being used in E are available at that point and all the execution paths leading from P evaluate the value of E.

Algorithm:

Lazy-Code Motion:

Step 1: Find all the anticipated expressions using a backward data-flow pass at each point in the code.

Step 2: Place the expressions where their values are anticipated along some other execution path. 

Step 3: Find the postponable expressions using a forward data-flow pass and place at a point in the code where they cannot be postponed further. 

Postponable expressions are those which are anticipated at some point in the code but they are not used for a long span of flow of code.

Step 4: Remove all the temporary variables assignment that are used only once in the complete code.


Like Article
Suggest improvement
Share your thoughts in the comments

Similar Reads