C++AMP中array、array_view、extent类和平铺介绍

原文地址:http://blog.csdn.net/lee353086/article/details/39937139    作者:kagula

C++ AMP 介绍(二)

最后更新日期:2014-05-02

阅读前提:《C++ AMP介绍(一)》

环境:Windows 8.1 64bit英文版,Visual Studio 2013 Update1英文版,Nvidia QuadroK600 显卡

内容简介

         介绍C++ AMP的 array、array_view、extent类和平铺的知识。

正文

数据的移动

         array和 array_view两个数据容器(模板类)用于把数据从运行时库(CPU)移到加速器(显卡或通用计算卡)上,array类在构造时建立数据的深拷贝,把数据复制到加速器(GPU)上,而array_view类是个包装类,仅仅当核心函数(kernel function)要用到数据时,才把源数据复制到加速器上。

[cpp]  view plain  copy
  在CODE上查看代码片 派生到我的代码片
  1. #include <amp.h>  
  2. using namespace concurrency;  
  3.   
  4. //演示array类的使用方式  
  5. void test_array()  
  6. {  
  7.     //测试数据  
  8.     std::vector<int> data(5);  
  9.     for (int count = 0; count < 5; count++)  
  10.     {  
  11.         data[count] = count;  
  12.     }  
  13.   
  14.     //构造array实例  
  15.     array<int, 1> a(5, data.begin(), data.end());  
  16.   
  17.     parallel_for_each(  
  18.         a.extent,  
  19.         [=, &a](index<1> idx) restrict(amp)  
  20.     {  
  21.         a[idx] = a[idx] * 10;  
  22.     }  
  23.     );  
  24.   
  25.     //array实例a不需要调用同步方法  
  26.     //但是需要赋值给data  
  27.     data = a;  
  28.   
  29.     //输出0,10,20,30,40  
  30.     for (int i = 0; i < 5; i++)  
  31.     {  
  32.         std::cout << data[i] << "\n";  
  33.     }  
  34. }  


        array_view同array之间几乎有相同的成员,但是它们底层的行为不一样,所以当你建立两个指向同一个数据源的array_view实例时,实际上它们指向同一个内存地址,数据只有当需要的时候才会被复制到加速器中,所以你得注意数据的同步,array_view类的主要好处是数据仅当要被加速器用到的时候才会被移动。

   共享内存是能被CPU和GPU访问的内存,array类可以控制共享内存的存取方式,但是首先我们需要测试加速器是不是支持共享内存,下面是array使用共享内存的示例代码。

[cpp]  view plain  copy
  在CODE上查看代码片 派生到我的代码片
  1. int test_sharedMemory()  
  2. {  
  3.     // 一台计算机中可能有多块加速器,取默认加速器  
  4.     accelerator acc = accelerator(accelerator::default_accelerator);  
  5.   
  6.     // 测试默认加速器是否支持共享内存  
  7.     if (!acc.supports_cpu_shared_memory)  
  8.     {  
  9.         std::cout << "The default accelerator does not support shared memory" << std::endl;  
  10.         return 1;  
  11.     }  
  12.   
  13.     // 设置cpu默认存取方式  
  14.     acc.set_default_cpu_access_type(access_type_read_write);  
  15.   
  16.     //为acc加速器建立accelerator_view(加速器视图)实例  
  17.     //读写方式默认为加速器default_cpu_access_type属性的设定  
  18.     accelerator_view acc_v = acc.default_view;  
  19.   
  20.     // extent指示array实例建立一个含10个元素的一维数组  
  21.     extent<1> ex(10);  
  22.   
  23.     // 指定加速器视图,输入数组在CPU上只写  
  24.     array<int, 1> arr_w(ex, acc_v, access_type_write);  
  25.   
  26.     // 指定加速器视图,输出数组在CPU上只读  
  27.     array<int, 1> arr_r(ex, acc_v, access_type_read);  
  28.   
  29.     // 指定加速器视图,可以在CPU上读写的数组  
  30.     array<int, 1> arr_rw(ex, acc_v, access_type_read_write);  
  31.   
  32.     return 0;  
  33. }  


 

index 类

   index类指定元素在array或array_view对象中的位置,下面是index类的使用示例代码

[cpp]  view plain  copy
  在CODE上查看代码片 派生到我的代码片
  1. void test_indexClass()  
  2. {  
  3.     int aCPP[] = { 1, 2, 3,  
  4.         4, 5, 6 };  
  5.     //新建2维(两行三列)array_view包装器  
  6.     array_view<int, 2> a(2, 3, aCPP);  
  7.     index<2> idx(1, 2);  
  8.   
  9.     //输出6  
  10.     std::cout << a[idx] << "\n";  
  11. }  


 

extent类

         虽然extent类在很多场合下不是必要的,但是微软的部分示例代码使用到了extent class,所以有必要介绍下extent class。

         extentclass用来指定array或array_view各个维度的元素数量,你可以使用extent class建立array或array_view对象,也可以从array或array_view对象中存取extent,下面的例子演示了extent class的使用。

[cpp]  view plain  copy
  在CODE上查看代码片 派生到我的代码片
  1. void test_extentClass()  
  2. {  
  3.     int aCPP[] = { 111, 112, 113, 114,   
  4.                    121, 122, 123, 124,   
  5.                    131, 132, 133, 134,  
  6.   
  7.                    211, 212, 213, 214,  
  8.                    221, 222, 223, 224,  
  9.                    231, 232, 233, 234 };  
  10.     extent<3> e(2, 3, 4);  
  11.     array_view<int, 3> a(e, aCPP);  
  12.   
  13.     //断言extent[0],[1],[2]的属性分别为2、3、4  
  14.     assert(2 == a.extent[0]);     
  15.     assert(3 == a.extent[1]);  
  16.     assert(4 == a.extent[2]);     
  17. }  


parallel_for_each函数

         我们在上篇文章中调用过parallel_for_each函数,它有两个入口参数,第一个入口参数为计算域,是个extent或tiled_extent对象,定义了要在加速器上并发运行的线程集合,它会为每个元素生成一根用于计算的线程。第二个参数是lambda表达式,定义了要在每根线程上运行的代码。

 

加速代码:  砖面(Tiles )和边界(Barriers)

   将全体线程划分为若干个具有相等数量矩形(M*N根)线程集合,每个集合称为tile(砖面),多个tile(砖面)组成全体线程,叫做平铺(tiling)。

   若要使用平铺,在parallel_for_each 方法中的计算域上调用 extent::tile 方法,并在 lambda 表达式中使用 tiled_index 对象。

         下面是两张来自微软官网的砖面(tile)的组织图,可以看到如何索引元素。

图中的idx是index类,sample是全局空间(array或array_view对象)

下图中的t_idx是index类,descriptions是全局空间(array或array_view对象)



下面这个来自微软官方的例子,每2*2=4根线程组成一个砖面(tile),计算砖面(tile)中元素的平均值。

[cpp]  view plain  copy
  在CODE上查看代码片 派生到我的代码片
  1. void test_tile()  
  2. {  
  3.     // 测试样本:  
  4.     int sampledata[] = {  
  5.         2, 2, 9, 7, 1, 4,  
  6.         4, 4, 8, 8, 3, 4,  
  7.         1, 5, 1, 2, 5, 2,  
  8.         6, 8, 3, 2, 7, 2 };  
  9.   
  10.     // The tiles(下面是6个砖面):  
  11.     // 2 2    9 7    1 4  
  12.     // 4 4    8 8    3 4  
  13.     //  
  14.     // 1 5    1 2    5 2  
  15.     // 6 8    3 2    7 2  
  16.   
  17.     // averagedata用来存放运算结果:  
  18.     int averagedata[] = {  
  19.         0, 0, 0, 0, 0, 0,  
  20.         0, 0, 0, 0, 0, 0,  
  21.         0, 0, 0, 0, 0, 0,  
  22.         0, 0, 0, 0, 0, 0,  
  23.     };  
  24.   
  25.     //每四个元素(四根线程)组成一个tile(砖面),所以共有六个tile(砖面)  
  26.     array_view<int, 2> sample(4, 6, sampledata);  
  27.     array_view<int, 2> average(4, 6, averagedata);  
  28.   
  29.     //通过[1]extent.tile代替extent[2]tiled_index代替index,启用平铺模式  
  30.     parallel_for_each(  
  31.         // 把extent切分为以2*2为单位的tile(砖面)  
  32.         sample.extent.tile<2, 2>(),  
  33.         [=](tiled_index<2, 2> idx) restrict(amp)  
  34.     {  
  35.         //tile_static关键字的变量范围是整个tile(砖面)  
  36.         //所以每个tile(砖面)(2*2=4根线程)只实例化一个tile_static  
  37.         tile_static int nums[2][2];  
  38.   
  39.         //tile(砖面)中的所有线程分别运行下面的代码  
  40.         //把值复制到tile_static实例nums,所以同一个nums会被赋值2*2=4次  
  41.         nums[idx.local[1]][idx.local[0]] = sample[idx.global];  
  42.   
  43.         //等待tile(砖面)中的所有线程,运行完上面这段代码  
  44.         idx.barrier.wait();  
  45.   
  46.         //现在nums中的2*2=4个元素已经有有效值了  
  47.         //tile(砖面)中的所有线程再次分别运行下面的代码  
  48.   
  49.         //计算平均值,  
  50.         int sum = nums[0][0] + nums[0][1] + nums[1][0] + nums[1][1];  
  51.   
  52.         //把计算结果复制到array_view对象中.  
  53.         average[idx.global] = sum / 4;  
  54.     }  
  55.     );  
  56.   
  57.     //打印运算结果  
  58.     for (int i = 0; i < 4; i++) {  
  59.         for (int j = 0; j < 6; j++) {  
  60.             std::cout << average(i, j) << " ";  
  61.         }  
  62.         std::cout << "\n";  
  63.     }  
  64.   
  65.     // Output:  
  66.     // 3 3 8 8 3 3  
  67.     // 3 3 8 8 3 3  
  68.     // 5 5 2 2 4 4  
  69.     // 5 5 2 2 4 4  
  70. }  


   使用平铺的好处是,从tile_static变量存取数据要比从全局空间(array和array_view对象)要快。为了从平铺中得到性能优势,我们的算法必须把计算域拆分为tile(砖面)然后把数据放到tile_static变量中加快数据存取速度。

   注意不要使用类似下面的代码来累加tile(砖面)中的数据,

tile_static  float  total;

total  +=  matrix[t_idx];‘

原因[1]total的初始值是不确定的,所以第二句代码的运算没有意义。

原因[2]由于tile(砖面)中的多根线程竞争同一个title_static变量,计算结果会不确定。

 

内存屏障(MemoryFences)

         在restrict(amp)限定中,有两种内存必须要同步:

全局内存:array或array_view实例

tile_static内存:tile(砖面)内存

         内存屏障确保两种内存的线程同步,要调用内存屏障可以使用下面三种方法:

         tile_barrier::wait(或tile_barrier::wait_with_all_memory_fence)方法: 建立全局内存和tile_static内存的屏障。

   tile_barrier::wait_with_global_memory_fence方法 : 仅建立全局内存的屏障

   tile_barrier::wait_with_tile_static_memory_fence 方法 :仅建立tile_static内存的屏障

   调用特定类型的屏障(fence)可以提高你应用的性能,在下面的例子中 tile_barrier::wait_with_tile_static_memory_fence 方法的调用代替tile_barrier::wait方法的调用提高了应用的性能。

[cpp]  view plain  copy
  在CODE上查看代码片 派生到我的代码片
  1. // 使用tile_static内存屏障  
  2. parallel_for_each(matrix.extent.tile<SAMPLESIZE, SAMPLESIZE>(),  
  3.      [=, &averages](tiled_index<SAMPLESIZE, SAMPLESIZE> t_idx) restrict(amp)  
  4. {  
  5.     // 把数据从全局内存中复制到title_static内存中.  
  6.     tile_static floattileValues[SAMPLESIZE][SAMPLESIZE];  
  7.    tileValues[t_idx.local[0]][t_idx.local[1]] = matrix[t_idx];  
  8.    
  9.     // 等待title_static内存中的数据复制完毕  
  10.    t_idx.barrier.wait_with_tile_static_memory_fence();  
  11.    
  12.     // 如果你移除if语句,代码会把被tile(砖面)中的所有线程调用,这样每个  
  13.     // tile(砖面)中的元素都会被分配一个同样的平均值.  
  14.     if (t_idx.local[0] == 0&& t_idx.local[1] == 0) {  
  15.         for (int trow = 0; trow <SAMPLESIZE; trow++) {  
  16.             for (int tcol = 0; tcol< SAMPLESIZE; tcol++) {  
  17.                averages(t_idx.tile[0],t_idx.tile[1]) += tileValues[trow][tcol];  
  18.             }  
  19.         }  
  20.        averages(t_idx.tile[0],t_idx.tile[1]) /= (float) (SAMPLESIZE *SAMPLESIZE);  
  21.     }  
  22. });  


         restrict(amp)修饰的代码段是在加速器(GPU)上运行的,默认在里面的代码段,下断点不会Break(进入),在[Solution Explorer]窗口中点击项目名称,快捷键[Alt]+[Enter],打开当前项目属性页,[Configuration Properties]->[Debugging]->[Debugger Type]默认为“Auto”,改为“GPU Only”就可以Debug当前项目加速器(GPU)上运行的代码了。

         据官网介绍,无符号整数的处理速度要比带符号整数快,所以尽量用无符号整数吧。

参考资料

《Using Tiles》

http://msdn.microsoft.com/en-us/library/vstudio/hh873135.aspx

《使用平铺》

http://msdn.microsoft.com/zh-cn/library/vstudio/hh873135.aspx

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值