C++ AMP 学习笔记
C++ AMP是微软研究人员开发的一个并行计算库,有了它就可以让程序在为并行计算设计的硬件上运行(比如显卡)。和AMD鼓吹的OpenCL一样,C++ AMP也是一个开放的开发标准,所以与Nvidia的cuda不同,它并不依赖于特定的硬件(比如Nvidia的Geforce系列显卡)。目前微软提供了Visual Studio平台上的开发工具集(Intellisense, 调试,性能优化),未来可能会移植到其他平台上,比如UNIX或者Linux。
想用C++ AMP写程序,需要的配置有:
Windows 7或8操作系统;
Visual Studio 2012;
DirectX June 2010 SDK;
一块可以运行DirectX 11的显卡(大多数新显卡都可以)。
为什么要用显卡(GPU)做计算?
1. 显卡的浮点运算能力比CPU强。浮点运算能力一般用FLOPS(Floating Point Operations Per Second)来表示,1 GFLOPS = 1 GIGA FLOPS = 每秒十亿次浮点运算,1 TFLOPS = 1000 GFLOPS = 每秒一万亿次浮点运算。一颗最新的CPU(比如intel的i7系列)不过达到100GLOPS的运算能力,而AMD的7990系列显卡突破了8TFLOPS,几十倍于CPU。这是因为GPU拥有着更多的晶体管,因而也有着更多的计算资源,可以在计算速度方面完爆CPU。
2. GPU的功耗比CPU低。CPU的功耗大概可以低到1 Gflop/Watt,也就是每瓦电力可以提供1Gflops的计算能力。GPU的功耗可以达到10 Gflop/Watt。相当于消耗同样的电力,GPU可以做十倍的运算。
既然显卡计算能力这么强,为什么大多数人还是在CPU上编程呢?因为显卡并没有CPU那么完善的数据处理能力——例如多任务,读入/写出,随机存储等等。在面临复杂的算法时,还是CPU的处理显得游刃有余。所以GPU对现行计算的优化还只能集中在可以高度并行化的算法上,比如矩阵计算,图像处理等等。
说了这么多显卡,回来再说两句C++ AMP。微软本着“简约而不简单”的原则,一共只给C++增加了两个关键词:parallel_for_each和restrict。从名字可以看出来第一个是用来写并行代码的,restrict则是制定语言类型的(GPU执行还是CPU执行)。只要不在并行代码的部分,你尽可以使用C++的各种特性:模板,C++标准库等等。微软还贴心的送上了显卡加速的数学库,比如三角函数,幂函数,指数函数和平方倒数等等。
好吧,让我们看看到底怎么用C++ AMP写程序:
#include
using namespace concurrency;
void AddArrays (int n, const int* const pA, const int * const pB, int * const pC)
{ array_view a (n, pA);
array_viewb (n, pB);
array_viewc (n, pC);
parallel_for_each (c.extent, [=] (index<1> idx) restrict(amp) {
c[idx] = a[idx] + b[idx];
});
}
可见一段完整的C++ AMP代码需要至少以下几个部分:
1. amp.h头文件
2. 使用concurrency命名空间
3. 使用array_view将数据从内存拷贝至显卡
4. 使用parallel_for_each和lambda函数(之后会讲)完成运算
5. 使用restrict(amp)告诉编译器这一段是显卡加速代码
这段代码的目的是把两个一维数组a和b拷贝至显卡,然后把它俩的每个元素逐个相加后得到数组c,再把c拷贝回内存。我们先把这段代码放一会,看看C++ AMP中经常用到的几个概念。
*** array ***
并行计算的对象绝大多数是数组,array就是放在显卡上的一个数组。第一个参数T是数组类型,int代表整形,float代表单精度实数,double代表双精度实数。第二个参数N是数组的秩,N=1时为一维数组,T=2时为二维数组,最大可以定义128维数组(真的需要这么多维么)。
如果声明了一个array b(4,2),则代表这是一个二维实数数组,大小为4X2,也就是说一共4行2列,合计8个元素。
可以在声明array的时候顺便把内存里的数据也拷贝过来,像这样:
array a(5, v.begin(), v.end());
这里v是内存里的一个STL vector,被从头至尾拷贝至显卡中的a数组。
当然也可以先声明再拷贝:
array a (5);
copy(a,v);
*** extent ***
C++ AMP也允许把数组大小单独作为一个变量,叫做extent。像这样:
concurrency::extent<3> e(2,3,4); // e 将被用来规定一个三维数组的大小
array m(e, v.begin()); //生成一个显卡上的三维数组m,数组大小为2X3X4.
*** array_view ***
说完了array,之前那个例子中的array_view是干什么用的呢?如果说array相当于C++中一个独立变量的话,array_view则相当于一个指向变量的别名(reference)。array_view在创建时必须被初始化,也就是说它必须“有料”,这个“料”指的是内存上的某个数组,array_view就挂靠在这个内存名下。array则不同,前面已经提到过可以声明一个只有大小没有数据的array。如果改变了array_view的数据,它指向的那块内存区域的数据也会被改变。
干嘛放着好好的array不用去折腾这个array_view呢?这恰恰是微软研究人员的苦心所在:array_view给程序员免除了把数据从内存拷贝到显卡再从显卡拷贝至内存这个繁琐的过程。反正array_view是内存的好基友,在array_view中做的计算会被自动同步到内存中。
之前那段示例代码,如果用array写,会变成这样:
void AddArrays (int n, const int* const pA, const int * const pB, int * const pC)
{ array a (n);
copy (pA, a);
array b (n);
copy (pB, b);
arrayc (n);
parallel_for_each (c.extent, [=] (index<1> idx) restrict(amp) {
c[idx] = a[idx] + b[idx];
});
copy(c, pC);
}
可见使用array_view的好处。
*** parallel_for each ***
parallel_for_each是微软专门为C++ AMP添加的关键字,也是C++ AMP的核心。这里就是放并行代码的地方。再看下示例程序中的parallel_for_each 部分:
parallel_for_each (c.extent, [=] (index<1> idx) restrict(amp) {
c[idx] = a[idx] + b[idx];
});
parallel_for_each的第一个参数c.extent告诉了显卡需要多少线程来进行计算。如果c是一个4x3x2的数组的话,则c.extent=(4,3,2),显卡会并发出24个线程。
parallel_for_each 的第二个参数[=] (index<1> idx)被称为lambda 函数。lambda函数是一个很有趣的迷你函数,它没有函数名,却有完整的函数体:
{
c[idx] = a[idx] + b[idx];
});
[=]叫做lambda函数的捕获从句(好小的从句),index<1> idx是lambda函数的参数。这样一个小小的函数几乎把C++里的括号类型用完了:[], (), {}。
为啥[]里是个=号呢?这是因为c数组的计算结果是通过值传递到内存的。如果用reference(别名)传递,就得用[&]了————好吧我也不知道自己在说啥。若想更深入学习C++的lambda函数,请猛戳:
http://msdn.microsoft.com/zh-cn/library/dd293608.aspx
C++ AMP对于这个lambda函数有什么要求呢?其实要求还挺多的。
1.lambda函数不可有返回值(这就要求它必须是void型)。
2.lambda函数一般只能有一个参数,就是线程号(例子中的idx)。
3.结尾必须是restrict (amp)。
4.它只能调用C++ AMP许可的数据类型。这些类型包括int, unsigned int, float, double, C型数组,array_view, array, 或者只包括以上类型的结构体。
下面这些类型是不能用的:
bool, char, short, long long, 以及这些类型的unsigned版本。
*** restrict (amp) ***
最后罗嗦几句这个restrict给C++ AMP带来的限制。其实lambda函数体内还可以调用其他的C++函数,但是既然是并行计算嘛,就得有些限制:
1.所有的函数都必须在编译代码时即确定的!这意味着被调用的C++函数必须是和lambda函数在同一个.cpp文件之中,或者在.cpp文件所包含的头文件之中。
2.如果非要在并行计算中使用类,请注意虚拟函数是不能用了。虚拟继承当然更没戏了。下面各种花哨的C++特性统统不能用:
今天就写到这吧,以后有时间继续八。
- 递归
- new / delete
- goto
- try/throw/catch
- 全局变量,静态变量
- 内嵌汇编
文章来源:
如哥的博客
上一篇: ST算法_求RMQ问题_时间复杂度O(n*log2(n))+O(1)
下一篇: DAY32