Download presentation

Presentation is loading. Please wait.

Published byShemar Mayers Modified over 2 years ago

1
λλ fernando@dcc.ufmg.br Divergence Analysis with Affine Constraints Diogo Sampaio, Sylvain Collange and Fernando Pereira The Federal University of Minas Gerais - Brazil Programming Languages Laboratory λλ

2
λλ fernando@dcc.ufmg.br The Objective of this work is to speedup code that runs on GPUs. We will achieve this goal via two contributions. Divergence analysis with affine constraints. Divergence aware register allocation. Which enables…

3
λλ fernando@dcc.ufmg.br Motivation General Purpose Programming in Graphics Processing Units is a reality today. – Lots of academic research. – Many industrial applications Yet, programming efficient GPGPU applications is hard. – Complex interplay with the hardware. – Threads execute in lock step, but divergences may happen.

4
λλ fernando@dcc.ufmg.br What are Divergences? Below we have a simple kernel, and its Control Flow Graph: __global__ void ex (float* v) { if (v[tid] < 0.0) { v[tid] /= 2; } else { v[tid] = 0.0; } Why do we have divergences in this kernel?

5
λλ fernando@dcc.ufmg.br Why are Divergences a Problem?

6
λλ fernando@dcc.ufmg.br Uniform and Divergent Variables If a variable has always the same value for all the threads in execution, then we call it uniform. If different threads in execution may see the same variable name with different values, this variable is called divergent. Which variables are divergent? – The thread identifier is always divergent. – Variables that depend on divergent variables are also divergent. Data dependences. Control dependences.

7
λλ fernando@dcc.ufmg.br Data Dependences If a variable v is defined by an in instruction that uses a variable u, then v is data-dependent on u. In the figure, %r1 depends on v and on %tid. The value of %r1 may be different for different threads.

8
λλ fernando@dcc.ufmg.br Control Dependences If the value assigned to a variable v is controlled by a variable u, then v is control-dependent on u. In the figure, %f2 is control dependent on %p1. Depending on how each thread branches at the end of B 0, %f2 may be %f1 / 2 or 0.0 at B ST.

9
λλ fernando@dcc.ufmg.br Affine Variables Some divergent variables are special: they are affine expressions of the thread identifier, e.g,. v = C× T id + N. Example: the kernel below computes the average of each column of a matrix: The loop always executes the same number of iterations for all the threads

10
λλ fernando@dcc.ufmg.br Affine Variables Variable i is divergent, yet, it is very regular: each thread sees it as " T id + N × c", where N is the current loop iteration. We say that i is an affine variable. In this case, i = T id + 10 * c

11
λλ fernando@dcc.ufmg.br This analysis classifies variables as uniform, affine or divergent. Our divergence analysis is a dataflow analysis. – We associate an abstract state with each variable. – This abstract state is a pair (a, b), which means a × T id + b. – Each element in the pair can be: A constant, which we denote by 'C' A non-initialized value, which we denote by '?' An unknown value, which we denote by 'D' The Divergent Analysis with Affine Constraints

12
λλ fernando@dcc.ufmg.br Uniform Variables A uniform variable v is bound to the state (0, X), which means 0 × T id + X. – If X is a known constant, then v is a constant. No worries: we shall explain how we find these abstract states!

13
λλ fernando@dcc.ufmg.br Divergent Variables A divergent variable v is bound to the state (D, D), which means that we do not know anything about the runtime values that this variable can assume. No worries: we shall explain how we find these abstract states!

14
λλ fernando@dcc.ufmg.br Affine Variables An affine variable v is bound to the state (c, X), which means c × T id + X. The factor c is always a known constant, X can be either a known constant, or D. Ok: it is about time to explain how we find these abstract states.

15
λλ fernando@dcc.ufmg.br Solving Divergence Analysis Initially every variable is bound to the abstract state (?, ?), unless… It is initialized with a constant, e.g., if we have the assignment v = 10, then [ v ] = (0, 10). Unless…. It is initialized with a constant expression of T id, e.g., if v = 10 * T id + 3, then [ v ] = (10, 3). Unless… The variable is a function parameter, and its abstract state is (0, D). Once we have initialized every variable, then we start iterating a few propagation rules, until we reach a fixed point.

16
λλ fernando@dcc.ufmg.br The Propagation Rules There are many different propagation tables (we call them dataflow equations). – We have one table for each different program instruction. blue cantaloupe – Lets consider, for instance, that the program contains an instruction v = v 1 + v 2. The abstract state of v 1, e.g., [ v 1 ] is given by the blue column, and [ v 2 ] by the cantaloupe. +(0, b 1 )(0, D)(a 1, b 1 )(a 1, D)(D, D) (0, b 2 )(0, b 1 +b 2 )(0, D)(a 1 +a 2, b 1 +b 2 )(a 1 +a 2, D)(D, D) (0, D) (a 1, D) (D, D) (a 2, b 2 )(a 2, b 1 +b 2 )(a 2, D)(a 1 +a 2, b 1 +b 2 )(a 1 +a 2, D)(D, D) (a 2, D) (a 1 +a 2, D) (D, D)

17
λλ fernando@dcc.ufmg.br Applying the Rules We work on the program dependence graph. Variables to be processed are placed in a worklist.

18
λλ fernando@dcc.ufmg.br Applying the Rules Where there is any variable v in the worklist, we try to process the instructions that use v.

19
λλ fernando@dcc.ufmg.br Applying the Rules If all the dependences of a variable v have been processed, then we can remove v from the worklist. If we process an instruction that defines variable w, then we add w to the worklist. We have removed T id from the worklist, and added i 0 to it.

20
λλ fernando@dcc.ufmg.br Reaching a Fixed Point We keep performing this abstract interpretation, until the worklist is empty. – This happens once we reach a fixed point.

21
λλ fernando@dcc.ufmg.br How to Use the Divergence Analysis There are many compiler optimizations that need the information provided by the divergence analysis. We are using the results of our divergence analysis with affine constraints to guide a register allocator. – We call it The Divergence Aware Register Allocator. Divergence analysis with affine constraints. Divergence aware register allocation.

22
λλ fernando@dcc.ufmg.br What is Register Allocation? Register allocation is the problem of finding locations for the variables in a program. Variables can stay in registers or in memory. – Variables sent to memory are called spills. In Graphics Processing Units we have roughly three types of memory: – Local: outside-chip and private to each thread. – Global: outside-chip and visible to every thread. – Shared: inside-chip and visible to every thread ( in the same warp – lets abstract this detail away ).

23
λλ fernando@dcc.ufmg.br The Key Insight: where to place spills A traditional allocator moves every spilled variable to the local memory. However, we can do much better: – Uniform spilled variables can be placed in the shared memory. – And affine spilled variables can be also placed in the shared memory. But this is a bit trickier, and I shall explain it later.

24
λλ fernando@dcc.ufmg.br Example 0×Tid + D c×Tid + DD×Tid + D Uniform Affine Divergent

25
λλ fernando@dcc.ufmg.br

26
λλ Redundancy: Uniform variables always have the same value for all the threads. Would it not be better to keep only one image of each spilled uniform variable? Moreover, we can also share affine variables, as we will explain soon.

27
λλ fernando@dcc.ufmg.br This is what we get with divergence aware allocation

28
λλ fernando@dcc.ufmg.br A traditional allocator spills everything to the local memory. The divergent aware allocator uses more the shared memory. This has many advantages: – Shared memory is faster. – Less memory is used to spill variables. The benefits of our allocator

29
λλ fernando@dcc.ufmg.br How to Spill Affine Values? An affine value is like C× T id + N, where C is a constant known at compilation time. Lets assume an expression like: N = 2*t id + t0 store: st.local N 0xFFFFFC32 Load: ld.local N 0xFFFFFC32 changes to: st.shared t0 0xFFFFFC32 changes to: ld.shared t0 0xFFFFFC32 N = 2*t id + t0

30
λλ fernando@dcc.ufmg.br Implementation We have implemented the affine analysis and the divergence aware register allocator in Ocelot, an open source PTX optimizer. – More than 10,000 lines of code! – This compiler is used in the industry. We have successfully tested our divergence analysis in all the 177 different CUDA kernels that we took from the Rodinia and NVIDIA SDK 3.1 benchmark suites.

31
λλ fernando@dcc.ufmg.br Performance % faster than naive linearscan execution Gtx 570 / Nvidia CUDA driver and toolkit 3.2 / 32 bit linux / 8 register per thread % faster (execution time)

32
λλ fernando@dcc.ufmg.br Conclusions New directions to divergence aware optimizations. – So far, optimizations have been focusing on branch fusion and synchronization of divergent threads. Open source implementation already been used by the Ocelot community. To know more: – http://code.google.com/p/gpuocelot/ – http://simdopt.wordpress.com Questions?

33
λλ fernando@dcc.ufmg.br What if the affine expression is formed by constants only? If the affine expression is like C 0 × T id + C 0, where C 0 and C 1 are constants, then we do not need neither loads nor stores (this is rematerialization). For instance, assume N = 2*t id + 3 store: st.local N 0xFFFFFC32 Load: ld.local N 0xFFFFFC32 the store is completely removed changes to: N = 2*t id + 3 We have all the information to reconstruct N!

34
λλ fernando@dcc.ufmg.br Classification of spilled Variables. Uses (loads) Definitions (stores)

Similar presentations

© 2017 SlidePlayer.com Inc.

All rights reserved.

Ads by Google