Loop-invariant code motion

In computer programming, loop-invariant code consists of statements or expressions (in an imperative programming language) which can be moved outside the body of a loop without affecting the semantics of the program. Loop-invariant code motion (also called hoisting or scalar promotion) is a compiler optimization which performs this movement automatically.

Example

If we consider the following code sample, two optimizations can be easily applied.

for (int i = 0; i < n; i++) {
    x = y + z;
    a[i] = 6 * i + x * x;
}

The calculation x = y + z and x * x can be moved outside the loop since within they are loop invariant they do not change over the iterations of the loop so the optimized code will be something like this:

x = y + z;
t1 = x * x;
for (int i = 0; i < n; i++) {
    a[i] = 6 * i + t1;
}

This code can be optimized further. For example, strength reduction could remove the two multiplications inside the loop (6*i and a[i]), and induction variable elimination could then elide i completely. Since 6 * i must be in lock step with i itself, there is no need to have both.

Invariant code detection

Usually a reaching definitions analysis is used to detect whether a statement or expression is loop invariant.

For example, if all reaching definitions for the operands of some simple expression are outside of the loop, the expression can be moved out of the loop.

Benefits

Loop-invariant code which has been hoisted out of a loop is executed less often, providing a speedup. Another effect of this transformation is allowing constants to be stored in registers and not having to calculate the address and access the memory (or cache line) at each iteration.

However, if too many variables are created, there will be high register pressure, especially on processors with few registers, like the 32-bit x86. If the compiler runs out of registers, some variables will be spilled. To counteract this, the inverse optimization can be performed, rematerialization. Loop-invariant expressions can be hoisted out of loops, thus improving run-time performance by executing the expression only once rather than at each iteration.

Example:

In the code fragment below, the expression (x + y) is loop invariant, and the addition can be hoisted out of the loop.

void f (int x, int y)
{
  int i;
 
  for (i = 0; i < 100; i++)
    {
      a[i] = x + y;
    }
}

Below is the code fragment after the invariant expression has been hoisted out of the loop.

void f (int x, int y)
{
  int i;
  int t;
 
  t = x + y;
  for (i = 0; i < 100; i++)
    {
      a[i] = t;
    }
}

Notes:

Some C compilers can hoist a subset of loop-invariant expressions (e.g. integer addition, subtraction, and multiplication), but few compilers can hoist a wide range of expressions (e.g. left shift, right shift, etc.).

Most compilers reduce expressions with sequence operators (i.e. &&, ||, and ?:) to if-then-else blocks early in the compiler, and most compilers fail to hoist loop-invariant expressions containing sequence operators.


http://www.compileroptimizations.com/category/hoisting.htm

Further reading