Data dependency

From HandWiki
Revision as of 19:05, 6 February 2024 by John Stpola (talk | contribs) (linkage)
(diff) ← Older revision | Latest revision (diff) | Newer revision → (diff)

A data dependency in computer science is a situation in which a program statement (instruction) refers to the data of a preceding statement. In compiler theory, the technique used to discover data dependencies among statements (or instructions) is called dependence analysis.

Description

Assuming statement [math]\displaystyle{ S_1 }[/math] and [math]\displaystyle{ S_2 }[/math], [math]\displaystyle{ S_2 }[/math] depends on [math]\displaystyle{ S_1 }[/math] if:

[math]\displaystyle{ \left[I(S_1) \cap O(S_2)\right] \cup \left[O(S_1) \cap I(S_2)\right] \cup \left[O(S_1) \cap O(S_2)\right] \neq \varnothing }[/math]

where:

  • [math]\displaystyle{ I(S_i) }[/math] is the set of memory locations read by [math]\displaystyle{ S_i }[/math],
  • [math]\displaystyle{ O(S_j) }[/math] is the set of memory locations written by [math]\displaystyle{ S_j }[/math], and
  • there is a feasible run-time execution path from [math]\displaystyle{ S_1 }[/math] to [math]\displaystyle{ S_2 }[/math].

This condition is called Bernstein Condition, named by A. J. Bernstein.

Three cases exist:

  • Anti-dependence: [math]\displaystyle{ I(S_1) \cap O(S_2) \neq \varnothing }[/math], [math]\displaystyle{ S_1 \rightarrow S_2 }[/math] and [math]\displaystyle{ S_1 }[/math] reads something before [math]\displaystyle{ S_2 }[/math] overwrites it
  • Flow (data) dependence: [math]\displaystyle{ O(S_1) \cap I(S_2) \neq \varnothing }[/math], [math]\displaystyle{ S_1 \rightarrow S_2 }[/math] and [math]\displaystyle{ S_1 }[/math] writes before something read by [math]\displaystyle{ S_2 }[/math]
  • Output dependence: [math]\displaystyle{ O(S_1) \cap O(S_2) \neq \varnothing }[/math], [math]\displaystyle{ S_1 \rightarrow S_2 }[/math] and both write the same memory location.

Types

True dependency (read-after-write)

A true dependency, also known as a flow dependency or data dependency, occurs when an instruction depends on the result of a previous instruction. A violation of a true dependency leads to a read-after-write (RAW) hazard.

1. A = 3
2. B = A
3. C = B

Instruction 3 is truly dependent on instruction 2, as the final value of C depends on the instruction updating B. Instruction 2 is truly dependent on instruction 1, as the final value of B depends on the instruction updating A. Since instruction 3 is truly dependent upon instruction 2 and instruction 2 is truly dependent on instruction 1, instruction 3 is also truly dependent on instruction 1. Instruction level parallelism is therefore not an option in this example.[1]

Anti-dependency (write-after-read)

An anti-dependency occurs when an instruction requires a value that is later updated. A violation of an anti-dependency leads to a write-after-read (WAR) hazard.

In the following example, instruction 2 anti-depends on instruction 3 — the ordering of these instructions cannot be changed, nor can they be executed in parallel (possibly changing the instruction ordering), as this would affect the final value of A.

1. B = 3
2. A = B + 1
3. B = 7

Example:

 MUL R3,R1,R2
 ADD R2,R5,R6

It is clear that there is anti-dependence between these 2 instructions. At first we read R2 then in second instruction we are Writing a new value for it.

An anti-dependency is an example of a name dependency. That is, renaming of variables could remove the dependency, as in the next example:

1. B = 3
N. B2 = B
2. A = B2 + 1
3. B = 7

A new variable, B2, has been declared as a copy of B in a new instruction, instruction N. The anti-dependency between 2 and 3 has been removed, meaning that these instructions may now be executed in parallel. However, the modification has introduced a new dependency: instruction 2 is now truly dependent on instruction N, which is truly dependent upon instruction 1. As flow dependencies, these new dependencies are impossible to safely remove.[1]

Output dependency (write-after-write)

An output dependency occurs when the ordering of instructions will affect the final output value of a variable. A violation of an output dependency leads to an write-after-write (WAW) hazard.

In the example below, there is an output dependency between instructions 3 and 1 — changing the ordering of instructions in this example will change the final value of A, thus these instructions cannot be executed in parallel.

1. B = 3
2. A = B + 1
3. B = 7

As with anti-dependencies, output dependencies are name dependencies. That is, they may be removed through renaming of variables, as in the below modification of the above example:

1. B2 = 3
2. A = B2 + 1
3. B = 7

Implications

Conventional programs are written assuming the sequential execution model. Under this model, instructions execute one after the other, atomically (i.e., at any given point in time, only one instruction is executed) and in the order specified by the program.

However, dependencies among statements or instructions may hinder parallelism — parallel execution of multiple instructions, either by a parallelizing compiler or by a processor exploiting instruction-level parallelism. Recklessly executing multiple instructions without considering related dependences may cause danger of getting wrong results, namely hazards.

Relevance in Computing

Data dependencies are relevant in various areas of computing, particularly in processor design, compiler construction, parallel computing, and concurrent programming.

Processor design

  • Instruction pipelining: In pipelined processors, multiple instruction are executed in parallel in multiple pipeline stages. Thereby, data dependencies between registers must be respected and handled in the processor pipeline. Most relevant are true dependencies that are resolved e.g. by stalling the pipeline or operand forwarding.
  • Out-of-order execution: Modern processors often execute instructions out of their original order to improve performance. Thereby, name dependencies between registers must be respected (in addition to data dependencies) and are resolved e.g. by register renaming or scoreboarding. Data dependencies are also relevant for memory accesses and must be respected by memory disambiguation techniques that execute memory access instructions (loads and stores) out of program order.

Compiler construction

Data dependencies are relevant for various compiler optimizations, e.g.

  • Instruction scheduling: Compilers must schedule instructions in a way that respects data dependencies. This is crucial in optimizing compilers that rearrange code for better performance.
  • Loop transformations: In optimizing loops, compilers need to consider data dependencies to apply transformations like loop unrolling, fusion, or tiling without changing the semantics of the program.
  • Code motion: When a compiler considers moving a piece of code, it must ensure that data dependencies are not violated.

See also

References

  1. 1.0 1.1 John L. Hennessy; David A. Patterson (2003). Computer Architecture: a quantitative approach (3rd ed.). Morgan Kaufmann. ISBN 1-55860-724-2.