On Predicated Execution

目的:由于GPU属于SIMD架构,也就是单指令多数据,当一堆数据过来时,不一定同时跳转,因此这里通过预测寄存器来控制,也就是通过If-Conversion算法消除所有的跳转指令,这样带来的好处就是,将控制依赖转换为数据依赖,同时增大了Basic Block的大小,为有利于后面的指令调度。

 

这个算法其实很简单,我们可以这么理解:

  1. 每个Basic Block都要有一个PRF去控制当前的Block是否被执行,这里的控制,我们可以理解为,每个Block需要Use一个PRF去控制当前Block(假定BB2用P2去控制,那么BB2的所有的指令都会用P2去guard)。

  2. 既然需要一个PRF,那么这个PRF应该在哪里define比较好呢?

     

对1)来说就是考虑为每个Block分配哪个PRF去控制Block,也就是论文中的p = R(x)算法,这里的x指某一个Block,p指某一个PRF,比如上面提到的R(BB2) = p2;

对2)来说就是考虑每个Block中用的PRF应该在哪里去定义呢,也就是论文中的K(p) = {BB, BB…},还是上面的那个例子,假定p2在BB1和BB3中定义,那么K(p) = {BB1, BB3};

 

 

简单介绍了上面的算法后,首先要了解一个概念,即控制依赖,control dependency.我们需要明白的是,关于控制依赖的算法已经存在了,并不是在这里发明的,我们看看控制依赖的定义:

A node (basic block) Y iscontrol-dependent on another X iff

X determines whether Yexecutes, i.e.

• there exists a pathfrom X to Y s.t. every node in the path

other than X & Y ispost-dominated by Y

• X is notpost-dominated by Y

//参考ControlDependence.pdf

从上面的定义我们看到,其实在论文中讲到的算CD(t)就是来自于控制依赖的定义而已。控制依赖就解决了在哪定义的问题,也就是上面说到的K算法。

我们下面说说论文中的几个结论:

RK function:

    1. xy if CD(x)=CD(y)

      //如果Block x和y,都同时控制依赖一个集合,那么x≈y,约等于在//这里的意思是说x和y可以分得相同的PRF.

    2. Every node x belongs to one and only one class p=R(x).

      //每个Node x,也就每个Block,只需要用一个PRF去guard;

    3. Nodes with identical set K(p) of control dependencies belongto the same class p

//这个结论跟1)的结论是一致的,在后面的结论:

//p = R(x) iff K(p)=CD(x),这个结论的意思就是说,Block x用p

//去guard,K(p)表示预测寄存器p应该被定义的位置,其实就应该//被定义在Block x控制依赖的地方。

 

综上所述,RK算法就很好理解了,过程如下:

  1. 根据控制依赖的定义,算出每一个Block的控制依赖集合,也就是CD(x);

  2. 根据CD(x)分配PRF,也就是R算法,分配的原则也很简单,就是上面介绍的,如果CD(x) = CD(y),那么x和y分配相同的PRF;当Block x分配好了p,那么K(p)= CD(x);

  3. 最后一步,就是解决一个序列化的问题;

 

下面我们来说说为什么会有第三步,我们先看论文中给的例子:

Topological order

B1B3B2B4B5B6B7

  1. 我们在上面的图上看到,我们在原有控制流图的基础上多加了两个Block,也就是START和STOP,这两个Block除了分别用来标记控制流图的起始和结束外,START还有一个用处,就是给某些PRF初始化,可能有人要问了:所有的PRF不是已经在K(p)对应的Block中定义了吗?怎么还要在START中进行初始化呢?那么最后一个算法就是为了用来解决这个问题!

  2. 首先要理解一下,什么是序列化,序列化就是说,虽然程序中有控制流图,但需要按一定顺序文本化,就是上面列出的一种拓扑排序:

B1B3B2B4B5B6B7,按B1->B3->B2->B4->B5->B6->B7的顺序执行。加入我们不在START中将P4=F,按照这个拓扑排序执行,且t1= False, t3 = true ,会发现在执行B4时,P4是没有被初始化的,这是因为序列化后,B2对应的guard寄存器P2=False,因此B2这个Block中的指令没有执行,因此在B2中初始化的P4就没有被初始化,因此会有问题。

  1. 现在引出了我们算法第三步要解决的问题,先看论文给的结论:

    A(x) == {p P': exist path(x,Stop)such that K(p) ∩ path(x,Stop) =ϕ}

    这个意思是说,从Block x到Stop的所有可能的控制流路径中,如果存在某条路径上,没有经过K(p),也就说,p没有在这条路径上被初始化。那么我们需要计算的是A(Start),从Start到Stop的所有的路径中,是否有某个PRF没有被初始化,在上面的例子中,P4就在一条路径上没有被初始化,因此需要将其在Start进行初始化;

  2. 最后就来说说如何去得到这些没有被初始化的PRF,其实这个算法也是很容易理解的。这需要我们理解变量的活跃性分析,在论文中就是将这个问题转换成了变量的活跃性分析的问题,也就是如果在START的起始处还活跃的变量,说明这些变量这些变量是没有被初始化的。其实也可以通过计算在START结束处还活跃的变量,来说明这些变量仍然是活跃的,这两种都是可行的。

  

   Def(b) =  {p P': ±bK(p)}

  //计算在Block b中定义的PRF,凡是控制依赖Block b的Block

  //guardPRF,都是在b中定义的。

  Use(b) ={p ∈ P': p=R(b)}

  //用来guard Block b的PRF都是在b中使用的。

  IN(b) =Use(b)U(OUT(b)-Def(b))

  OUT(b) =U IN(s) for s ∈ succ(b)

  //这是活跃性分析的公式。

 

  

  

下面跟简单推倒下活跃变量分析的公式:

在计算活跃性分析的时候需要注意一点:1)自底向上计算,2) 从最后一条指令向上进行分析,为什么要这样呢?

  1. 因为最后的Block的LiveOut肯定是NULL;

  2. 从最后一条指令往上分析更加直观,比如上面的Block 3,我们先看最后一条指令 d = k + 1;在这里d被define了,那此时LiveIn(B3)中肯定不会有d,也就是说d要从B3的LiveIn活跃变量中删除;而

    d = k + 1中use了k,那么也就是k要加到LiveIn(B3),这就是

    IN(b) =Use(b)U(OUT(b)-Def(b))

  3. OUT(b) =U IN(s) for s ∈ succ(b),这个应该更好理解了,一个Block的LiveOut变量等于这个Block的所有succssor Block的LiveIn变量。就不多解释了。

     

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值