User:Adbecker/sandbox

In compiler theory, loop dependence analysis is the task of determining whether statements within a loop body form a dependence, with respect to array access and modification, induction, reduction and private variables, simplification of loop-independent code and management of conditional branches inside the loop body.

Loop dependence analysis is mostly done to find ways to do automatic parallelization, by means of automatic vectorization, shared memory or others.

Loop dependence analysis is a method that is used to find dependencies within a loop to exploit different forms of parallelism. Parallelism will help allow multiple processors to work on different statements within the loop in parallel of each other, which will decrease the total execution time of the program because it will allow the loop to be finished quicker.

Description
Loop dependence analysis occur on a normalized loop of the form:

where  may contain:

Where a is an m-dimensional array and,  , etc. are functions mapping from all iteration indexes (in) to a memory access in a particular dimension of the array.

For example, in C:

f1 would be, controlling the write on the first dimension of a and h2 would be  , controlling the read on the first dimension of b.

The scope of the problem is to find all possible dependencies between S1 and S2. To be conservative, any dependence which cannot be proven false must be assumed to be true.

Independence is shown by demonstrating that no two instances of S1 and S2 access or modify the same spot in array. When a possible dependence is found, loop dependence analysis usually makes every attempt to characterize the relationship between dependent instances, as some optimizations may still be possible. It may also be possible to transform the loop to remove or modify the dependence.

In the course of (dis)proving such dependencies, a statement S may be decomposed according to which iteration it comes from. For instance, S[1,3,5] refers to the iteration where,   and. Of course, references to abstract iterations, such as S[d1+1,d2,d3], are both permitted and common.

Loops can have a lot of overhead when executed sequentially. Depending on the number of total iterations of the loop, loops can take up a lot of your total execution time in a program. In order to reduce the total execution time and make your program run faster, loop dependence analysis is used to help exploit parallelism over different iterations in the loop. Running different iterations of the loop in parallel will help decrease the total execution time of the program as it will allow the loop to finish faster. In order to see how we can exploit parallelism, we have to first analyze the dependencies within the loop. The dependencies will help determine what statements in the loop need to be completed before another statement can start, and what statements in the loop can be executed in parallel with respect to the other statements in the loop. The types of dependencies that will be analyzed in the loop are data dependencies and control dependencies.

Data Dependence
Data dependencies show the relationships between the variables in the code. There are three different types of data dependencies:
 * True Dependence
 * Anti Dependence
 * Output Dependence

True Dependence
A true dependence occurs when a location in memory is written to before it is read. It introduces read-after-write (RAW) hazards because the instruction that reads from the location in memory has to wait until it is written to by the previous instruction or else the reading instruction will read the wrong value. An example of a true dependence is:

In this example, there is a true dependence between S1 and S2 because variable a is first written in statement S1 and then variable a is read by statement S2. This true dependence can be represented by S1 →T S2. A true dependence can also be seen when reading and writing between different iterations in a loop. The following example shows a true dependence between different iterations.

In this example, a true dependence exists between statement S1 in the jth iteration and S1 in the j+1th iteration. There is a true dependence because a value will be written to a[j] in one iteration and then a read occurs by a[j-1] in the next iteration. This true dependence can be represented by S1[j] →T S2[j+1].

Anti Dependence
An anti dependence occurs when a location in memory is read before that same location is written to. This introduces write-after-read (WAR) hazards because the instruction that writes the data into a memory location has to wait until that memory location has been read by the previous instruction or else the reading instruction would read the wrong value. An example of an anti dependence is:

In this example, there is an anti dependence between statements S1 and S2. This is an anti dependence because variable b is first read in statement S1 and then variable b is written to in statement S2. This can be represented by S1 →A S2. An anti dependence can be seen by different iterations in a loop. The following example shows an example of this case:

In this example, there is an anti dependence between the jth iteration of S1 and the j+1th element of S1. Here, the j+1th element is read before that same element is written in the next iteration of j. This anti dependence can be represented by S1[j] →A S1[j+1].

Output Dependence
An output dependence occurs when a location in memory is written to before that same location is written to again in another statement. This introduces write-after-write(WAW) hazards because the second instruction to write the value to a memory location needs to wait until the first instruction that writes to the same memory location finishes or else when we read the memory location at a later time, then it will read the wrong value. An example of an output dependence is:

In this example, there is an output dependence between statements S1 and S2. Here, the variable c is first written to in S1 and then variable c is written to again in statement S2. This output dependence can be represented by S1 →O S2. An output dependence can be seen by different iterations in a loop. The following code snippet shows an example of this case:

In this example, there is an output dependence between the jth element in S1 and the j+1th element in S2. Here, c[j+1] in statement S2 is written to in one iteration. In the next iteration, c[j] in statement S2, which is the same memory location as c[j+1] in the previous iteration, is written to again. This output dependence can be represented as S1[j] →O S2[j+1].

Control Dependence
In addition to data dependencies, control dependencies must be considered when analyzing dependencies between different statements in computer code. Control dependencies  are dependencies introduced by the code or the programming algorithm itself. One common example is a "goto" statement. This is where the code itself mandates the direction the program or thread will take. It is a goal of many computer programmers to eliminate control dependencies by explicitly changing control dependencies into data dependencies. One example of how this may be done could be done is through the use of "if" statements in lieu of "goto" statements. Through doing this, the program or thread will consider data before making a decision about what operation to perform next. However, even the "then" portion of a standard "if" statement introduces a control dependency. For instance, an instruction cannot be moved into or out of this portion of the code without changing the order in which operations sequentially occur.

Iteration vectors
A specific iteration through a normalized loop is referenced through an iteration vector, which encodes the state of each iteration variable.

For a loop, an iteration vector is a member of the Cartesian product of the bounds for the loop variables. In the normalized form given previously, this space is defined to be U1 × U2 × ... × Un. Specific instances of statements may be parametrized by these iteration vectors, and they are also the domain of the array subscript functions found in the body of the loop. Of particular relevance, these vectors form a lexicographic order which corresponds with the chronological execution order.

Dependence Vectors
To classify data dependence, compilers use two important vectors: the distance vector (σ), which indicates the distance between fn and hn, and the direction vector (ρ), which indicates the corresponding direction, basically the sign of the distance.

The distance vector is defined as σ = (σ1, ..., σk) where σn is σn = hn - fn

The direction vector is defined as ρ = (ρ1, ..., ρk) where ρn is:
 * (<) if σn > 0 => [fn < hn]
 * (=) if σn = 0 => [fn = hn]
 * (>) if σn [fn > hn]

A direction vector where the leftmost non = entry is not < can not exist. That would mean the sink of the dependency occurs before the source, which is not possible.

Loop Carried Dependence and Iteration Space Traversal Graphs
Iteration space traversal graphs (ITG) shows the path that the code takes when traversing through the iterations of the loop. Each iteration is represented with a node.

Loop carried dependence graphs (LDG) gives a visual representation of all true dependencies, anti dependencies, and output dependencies that exist between different iterations in a loop. Each iteration is represented with a node.

It is easier to show the difference between the two graphs with a nested for loop.

In this example, there is a true dependence between the j iteration of statement S1 and the j+1th statement of S1. This can be represented as S1[j,k] →T S1[j+1,k] The iteration space traversal graph and the loop carried dependence graph is:

Iteration Space Traversal Graph:

Loop Carried Dependence Graph: