原帖地址在:
请在这个地址进行讨论:
http://www.linuxfans.org/nuke/modules.php?name=Forums&file=viewtopic&t=73651
关于brookgpu的简要介绍看下面的链接:
http://tech.sina.com.cn/c/2003-12-30/26206.html
本文翻译了斯坦福大学网站上的关于brookgpu语言的一篇文章,原文在:
http://graphics.stanford.edu/projects/brookgpu/lang.html
关于brookgpu的用法在公社中有介绍,请看下面的网址:
http://www.linuxfans.org/nuke/modules.php?name=Site_Downloads&op=geninfo&did=2171
想使用brookgpu需要nvidia的显卡和cg的驱动。
ftp://download.nvidia.com/developer/cg/Cg_1.2.1/Linux/Cg-1.2.1-Linux.tar.gz
欢迎大家增加测试的数据和你对brookgpu的理解,初次翻译一个新的技术,错误在所难免,请大家多多批评指正!
我的mail:zhangyu9587@sina.com,欢迎你与我联系!
显卡代替CPU的语言-BrookGpu简介
Brook是标准ANSI C语言的扩展,它是一种为人们所熟悉的高效的语言,它集成了数据并行计算和算术计算集中这两个特点。
通用的计算模型,也就是所谓的流(Stream),同传统的常规语言相比,有以下两个主要的优点:
1.数据并行:允许编程者指定如何在不同的数据上并行地执行相同的操作。
2.算术计算集中:鼓励编程者指定数据上的操作,以达到全局的通信最小而局部的计算最大!
更多的关于Brook的内容可以在Merrimac web site 找到,里面包括了这种语言的完整的说明。Brookgpu为了在GPU上使用,它实现了Brook规范的一个子集。一个Brook 程序中包含合法的C代码和声明流(streams)的句法扩展以及叫做kernels的函数。
流(Streams)
流是一个新的数据类型的扩展,代表了一系列能够被并行处理的数据。Streams的声明规则和数组很像,都是使用尖括号进行声明的。声明一个流的例子如下所示:
float s <10, 10>;
上面声明了一个2维的浮点类型的流。每个流都是由流元素组成的。在上面的例子中,s是一个包含了100个浮点类型流元素的流。流的形状就是指流的维数。在这个例子中,流的形状是10×10。流是用列优先的方式来进行描述的,同C语言中的数组类似。因此,形状为<100>的流与形状为< 1,100>和<1,1,100>的流是等同的。
尽管同C数组很类似,但Brookgpu中的流同C语言中的数组有以下的区别:
1. 在kernels函数外不允许通过下标来获得流元素(例如 s[3][2])。
2. 不允许对流进行静态的初始化,例如:
float s<100>={1.0f,2.0f,....}是不允许的。
3. 流必须是局部变量(堆栈)。
4. 流只能在核心(kernel)函数中被读写或者是通过特殊的运算符从普通的指针和versa 获得数据。
streamRead(s,data_s);
/*用*data_s处的数据对流stream s<>进行赋值*/
streamWrite(s,data_s);
/*用流s<>的数据对*data_s进行赋值*/
尽管这个操作还可能通过编译器进行进一步的优化,但是它向流中进行拷贝已经是十分高效了。
对于GPU编译过的代码,流对应于组织内存(texture memory)的区域。编译器能进行代码流分析来更好地决定什么时候什么地点来分配组织内存(texture memory),或者完全消除流变量需要暂时存储的需要。为了方便起见,brook也扩展C来包括float2,float3和float4作为基本的数据类型。这些类型同C语言中用typedef 的结构(structs)是等价的。例如:
typedef stuct {
float x;
float y;
float z;
float w;
} float4;
另外,这些数据类型能够通过使用构造函数的语法来构造:
float4 a (0.2f,1.0f,3.2f,1.0f); // x=0.2f,y=1.0f ......
核心(kernels)
核心是在流上操作的特殊函数,是应用到每个输入流元素的一种并行函数。在一系列的输入流上调用一个核心函数就在每个流元素上实施了隐含的循环,即对每一个流元素调用核心体。通过GPU带有的核心函数,流被输入进视频存储器,核心函数被编译成由其“渲染”的分段程序。除了在声明核心时,它的前面要加上关键字 kernel,核心的定义和函数的定义非常的相似,返回类型通常都是void,并且其中的一个流参数要标记类型限定语'out'。全局内存空间和静态变量在kernels里无法被访问。一个kernel声明的例子如下所示:
kernel void k (float s<>,float3 f, float3 f, float a[10][10], out float o<>)
在这个例子中,输入流s的每一个元素都要调用kernel k的核心体。变量s在kernel体的内部的类型是float,为了每次隐含的调用,s被初始化为一个不同的元素。变量f是kernel中的一个常数,它在每次迭代中保持同一个值。在kernels内部,对输入流或是常数参数进行写操作都是不允许的。
变量a是一个二维数组,它可以通过标准C语言访问数组的方法来访问。a的维数并不需要被指定,然而如果指定了数组的维数,编译器可以更快地产生代码。每一个流都会以数组形式输入到一个核心(kernel)里面。
变量o是一个输出流。输出流是一个只写的参数,它的值由kernel函数体来进行赋值。在kernel体的内部,变量o的类型也是float。s的每一个元素都隐含地执行kernel体,产生输出流o的元素。注意到kernel的原型和函数体都受限于C和C++支持的cg/HLSL的子集。它们包括向量流类型,矩阵类型和标准的库函数。
想获得更多的信息,请参照CG语言规范,网址是:
http://developer.nvidia.com/object/linux_cg_toolkit.html
http://developer.nvidia.com/attach/3722
也可以参照HLSL文档,网址是:
http://msdn.microsoft.com/library/default.asp?url=/library/en- us/directx9_c/directx/graphics/reference/Shaders/HighLevelShaderLanguage.asp
调用一个kernel函数同调用任何C函数类似。
kernel void k(float s<>, float3 f, float a[10][10], out float o<>);
float a<100>;
float b<100>;
float c<10,10>;
streamRead(a, data1);
streamRead(b, data2);
streamRead(c, data3);
// 调用 kernel "k"
k (a, 3.2f, c, b);
streamWrite(b, result);
Reductions(约简)
Brook 提供对流进行并行约简的支持。约简是指将一个流转化为维数更小的流或一个单独的值的操作。这个操作可以由一个单独的,两输入的运算符来定义的,这个运算符必须是可结合的和可交换的。考虑到这个性质,流元素可以被看成是无序的集合,在运算过程中,那些元素可以以任意顺序结合直到产生出约简的结果。
约简函数只支持那些既能结合又能交换的约简。编译器必须能够以任意的顺序来计算约简。例如,计算一个流的和既是可结合的又是可交换的:
a+b+c+d =(a+b)+(c+d)=a+b+c+d
合法约简的例子是求和、相乘、求最大值或最小值、相或、相与、异或等位操作。不合法约简的例子包括减法和相除。我们必须注意的是编译器并不验证约简函数的合法性。所以编程者必须保证约简操作是合法的。声明一个不合法的约简将会导致进入未定义的执行状态。
约简函数的声明同kernel函数的声明类似,都有一些额外的限制。下面是一个约简函数的例子,它计算了一个浮点流的所有元素的和。
void reduce sum (float a<>,reduce float result<>) {
result = result + a;
}
约简函数只能带有两个流参数,一个输入流和一个输出流,输出流前有关键字reduce,其余的参数都不能是流。另外,两个流的类型必须相吻合。为了计算两个值的约简,kernel要允许对约简参数同时进行读和写。
传递到约简kernel的约简参数可以是标量值或是流。如果约简参数是一个标量,所有的流元素都将调用约简函数来产生一个单独的值,这个值被存放在约简变量中。约简的初始值被定义成输入流的第一个值。
多维约简
如果约简的参数是一个流,那么如何进行约简操作将是由输入流的维数和约简输出流维数之间的关系来决定的。
float s<100,200>;
float t;
sum(s, t); // 求s中所有元素的和,结果放在t中
float t<100, 1>;
sum(s, t);
float t<1, 200>;
sum(s, t);
调用约简kernel来约简元素就可以将输入流转换成不同维数的输出流。例如:
float s<100,200>;
float t<50, 20>;
sum(s, t);
在这个例子中,y方向上可以被2整除而x方向上可以被10整除。每生成t的一个元素,s的2×10个元素都要调用执行约简kernel。
如果维数不互相匹配,或是约简的维数比输入流的维数更大,或是输入流的维数不是偶数,都会产生编译的错误。
使用流的形状
为了生成在当今图形硬件上能有效运行的代码,brook for GPUS并没有实现在官方Brook规范说明中大多数流的运算符
(http://merrimac.stanford.edu/brookspec-v0.2.pdf)。但是,当调用kernels和隐式执行流操作时,Brook这种语言利用了输入流和输出流的相对形状。
基于流的形状,运算符streamStride和streamRepeat结合在一起并被隐式地调用。例如:
kernel void foo (float a<>, out float b<>);
float a<10,20>;
float b<50,10>;
foo(a,b);
在这个例子中,输出流b的y方向上比输入流a大5倍,然而输出流b的x方向上只有输入流a的一半大。对于b中的每一个流元素,kernel foo只被调用一次。流a隐式地改变大小来匹配输出流的大小。在这个例子中,在y方向上通过重复元素的方式 (1,1,1,1,1,2,2,2,2,2,3,3,3,3,3....)来使维数增加,而通过在x方向上每隔一个去掉一个元素的方法 (1,3,5,7,9....)来使维数减少。每个输入流都重复上面的操作。数组参数不受影响。
重复流
重复流是一种特殊类型的流,它预先就被初始化为一系列顺序的值(1,2,3,4,...)。创建一个重复流使用iter关键字和运算符:
iter float s<100> = iter(0.0f, 100.0f);
// s被初始化成0.0, 1.0, 2.0, ..., 99.0
iter操作符的第一个参数通常是流的初始值。第二个操作符是流的数值的上限,产生完全相同的形式【初始值,上限】。流元素之间的步长等于(初始值-上限)/流中元素的个数。
iter float s<100> = iter(0.0, 1.0f);
// s: 0.00, 0.01, 0.02, 0.03, ..., 0.99
iter float s<10> = iter(2.0f, 7.0f);
// s: 2.0, 2.5, 3.0, 3.5, ..., 6.5
一维重复流也同时与float2,float3,float4类型一起使用。每一个元素都被单独的插值。
iter float2 s<10> = iter(float2(0, 0), float2(10, 5));
// s: (0,0), (1,0.5), (2,1), ..., (9,4.5)。
目前的编译器只支持一维和二维的重复流。如果流是二维的,流元素的类型必须是float2。在二维的情况下,插值稍稍有些不同。
iter float2 s<4, 4> = iter (float2(0,0), float2(4, 10))
s:
(0, 0) (1, 0) (2, 0) (3, 0)
(0, 2.5) (1, 2.5) (2, 2.5) (3, 2.5)
(0, 5) (1, 5) (2, 5) (3, 5)
(0, 7.5) (1, 7.5) (2, 7.5) (3, 7.5)
流元素的每一个部分都会根据相应维数的最大和最小值进行插值。
目前Brookgpu编译器和运行不支持二维以上的重复流,更进一步地说,如果一个重复流传递进入一个kernel,它的维数必须和输出流的维数相匹配。这些限制可能会在将来发行的版本中取消,目前重复流和数据流并不相同,因此,重复流的参数中必须包含iter这个关键字,而且将重复流做为一个输出结果传进kernel也是不允许的。这些限制可能会在将来发行的版本中改变。
ScatterOp
streamScatterOp(s, index_stream, array,STREAM_SCATTER_ASSIGN);
StreamScatterop运算符执行一个间接的写操作,流s中包含传递给Scatter的数据,索引流(index Stream)中包含在数组中要写入数据的偏移量。第四个参数是执行如下的操作:将新来的数据同已经保存在数组中的数据结合起来,这个参数可以是一个约简函数,或是枚举函数的一个组成部分,例如STREAM_SCATTER_ASSIGN执行一个将流数据直接写入数组的写操作。
GatherOp
streamGatherOp(t, index_stream, array, STREAM_GATHER_FETCH);
streamGatherOp执行一个数组上间接的读操作。它通过使用索引流来生成一个取值的流(t)。如果数组是多维的,索引流提供一个在数组内的线性偏移(基于C语言数组的列优先排列)。
第四个参数可以是任何kernel函数,这个函数包含一个单独的输出流或是一个预定义的操作符。STREAM_GATHER_FETCH参数指出Gather操作仅仅读取值并将这个值放入流中就行了。