OpenCL 存储器次序规则

从根本上来说,一个关于存储器模型的问题就是理解对存储器中对象修改的时序。修改对象或通过函数调用来修改对象称为一个副作用,即改变了执行环境中的状态。通常对一个表达式的计算包括了值的计算以及副作用的引入。对于一个左值表达式的计算包括确定被赋值对象的同一性。本节的描述会比较学术化,如果读者对OpenCL标准本身不太感兴趣完全可以跳过,直接进入5.6.2节的内容。
我们假定OpenCL C内核编程语言以及主机端编程语言在由单个执行单元执行的某两个计算之间具有前后次序关系。该前后次序关系在那两个计算之间是一个非对称的、传递的(即离散数学中关于关系的对称性与传递性概念)、成对的关系,而且它们之间又是部分次序的。给定两个计算A和B,如果A的次序在B之前,那么对A的执行将先于对B的执行。当A的次序可能在B之前,也可能在B之后,那么A和B的计算在次序上是不确定的,而这在OpenCL标准中也未指定到底用哪种次序。
注意,前置次序(sequenced-before)是在单个执行单元(如一个主机端线程或一个工作项)中所执行的两个操作的部分次序。前置次序通常对应于那些操作的源程序次序,而之所以是部分次序,是因为OpenCL内核C语言中未定义实参计算次序。例如,在OpenCL C内核语言中调用foo(a+1,b+2,c+3);函数,参数计算次序是先执行a+1还是先执行c+3是未定义的,各家OpenCL平台实现厂商可以根据自己的需要来指定。
在OpenCL C内核编程语言中,在某一特定点,某个工作项W观察一个它可见的对象,那么W所看到的值可作为该对象的初始值,也可作为W在此对象中存储的值,或者作为另一个工作项或主机端线程在此对象中存储的值。这些根据以下规则来定。某些主机端编程语言的实现也会存在这种情况,对一个主机端线程可见的对象,它的值也可能是由另一个工作项或主机端线程对其所存储的值。
有两个表达式,如果其中一个修改了某个存储器位置的内容,而另一个同时对那个存储器位置进行读或写,那么这两个表达式就会存在冲突。
对某一特定的原子对象M的所有修改是以某个特定总和次序(见前面对memory_order_seq_cst的描述)发生的,称为对M的修改次序。如果A和B是对一个原子对象M的修改,并且A发生在B之前,那么A在对M的修改次序中将排在B之前。注意,对原子对象M的修改次序与M是在本地存储器还是在全局存储器无关。
一个释放次序产生于作用在原子对象M上的释放操作A。它作为在M的修改次序中所产生副作用的最大连续子序列。在这个子序列中,第一个操作是A,而后面每个操作要么由同一个工作项执行(或由执行释放操作的主机端线程执行),要么后面每个操作是原子的读-修改-写操作。
OpenCL的本地存储器与全局存储器是分离的。OpenCL内核程序对这两种存储器类型都可以访问,而主机端线程只能访问全局存储器。此外,OpenCL的work_group_barrier函数的flags参数指定了该函数将对哪些存储器可见。flags参数可设置为对本地存储器或全局存储器可见,也可设置为对二者都可见。由于可以设置存储器操作为本地存储器可见或全局存储器可见等,因此我们相应定义了本地同步和全局同步。在全局存储器上的某些操作可以全局同步于另一个工作项,或主机端线程所执行的其它操作。例如,在某一工作项上的“释放操作”,全局同步于另一个工作项上的“获得原子操作”。类似地,在内核程序中,某些本地存储器对象的原子操作可以本地同步于这些对象的其他原子操作。
我们定义了两个独立的前置次序的关系:全局前置次序以及局部前置次序。
一个全局存储器操作A全局发生在一个全局存储器操作B之前,如果:
1)A的次序在B之前;

2)A全局同步于B;

3)对于某个全局存储器操作C,A全局发生在C之前,并且C全局发生在B之前。这就是上面所提到的次序关系的传递性。
一个OpenCL实现应该确保不管在“本地前置次序”还是在“全局前置次序”关系中没有任何程序执行,哪怕是一个周期。
[插图]注意全局以及本地前置发生关系(order-befor)对于定义什么值被读以及数据竞争什么时候发生来说是很关键的。例如,全局前置次序关系定义了哪个全局存储器操作完全在其他全局存储器操作之前发生。如果一个操作A全局发生在操作B之前,那么A必须在B之前发生;特别是,A所写的任何内容将对B可见。本地前置发生关系对于本地存储器具有类似的特性。程序员可以使用本地存储器与全局存储器前置发生关系来思考设计程序行为的相关次序。
对全局对象M的一个可见副作用A,以及对M的一个值计算B(这里,M的值计算B对副作用A可见),同时满足以下条件:
1)A全局发生在B之前。
2)不存在其他对M的副作用X,使得A全局发生在X之前且X全局发生在B之前。也就是说,在副作用A与值计算操作B之间不存在其他对M的副作用。
我们类似地定义对本地对象M的对外可见的副作用。一个非原子的、要由计算B所确定的标量对象M的值,应该是由可见的副作用A所存储的值。
程序的执行包含了据竞争,如果它在两个不同的执行单元中包含了两个具有竞争性的行为A和B,并且满足:
1)A或B至少有一个不是原子的,或A和B不具有包含性的存储器作用域(例如,A操作的存储区域只对全局存储器可见,B操作的存储区域只对本地存储器可见;或者A操作和B操作的存储区域都是本地存储器,但是所操作的原子对象是在全局存储器中)。
2)A操作和B操作对于全局前后发生次序关系而言是无序的全局行为,或A和B操作对于本地前后发生次序关系而言是无序的本地行为。
任一这样的数据竞争都将导致未定义的行为。
我们也定义了本地原子对象与全局原子对象上副作用的可见次序。接下来将定义一个全局原子对象M的次序。本地原子对象上的副作用可见次序与使用本地前置发生关系所定义的类似。
作用在全局原子对象M上的副作用的可见序列,是在M的修改次序中所产生副作用的最大连续子序列。在此子序列中有对M的一次值计算B,而第一个副作用对B可见,并且每个副作用都全局发生在值计算B之前。B计算出来的M的值,应该是M可见序列中某个操作对M所存储的值。
如果对原子对象M进行修改的操作A,全局发生在对M修改的操作B之前,那么在M的修改次序中,A应该发生在B之前。这个要求称为写-写一致性。
如果对原子对象M的值计算A全局发生在对M的值计算B之前,并且A从对M的一个副作用X处取其值,那么由B计算出的值应该要么等于由X所存储的值,要么是对M的一个副作用Y所存储的值。这里,Y在对M的修改,在次序上发生在X之后。这个要求被称为读-读一致性。
如果对原子对象M的值计算A全局发生在对M的操作B之前,那么A将从对M的一个副作用X处取其值。这里X对M的修改在次序上应该在B之前。这个要求称为读-写一致性。
如果对原子对象M的一个副作用X全局发生在对M的值计算B之前,那么值计算B将从X处取其值,或从对M的修改次序中,在X之后的一个副作用Y处取其值。这个要求被称为写-读一致性。

赞(0)
未经允许不得转载:极客笔记 » OpenCL 存储器次序规则
分享到: 更多 (0)

评论 抢沙发

  • 昵称 (必填)
  • 邮箱 (必填)
  • 网址