C++ AMP 介绍(二)

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)要用到数据时,才把源数据复制到加速器上。

#include <amp.h>
using namespace concurrency;

//演示array类的使用方式
void test_array()
{
	//测试数据
	std::vector<int> data(5);
	for (int count = 0; count < 5; count++)
	{
		data[count] = count;
	}

	//构造array实例
	array<int, 1> a(5, data.begin(), data.end());

	parallel_for_each(
		a.extent,
		[=, &a](index<1> idx) restrict(amp)
	{
		a[idx] = a[idx] * 10;
	}
	);

	//array实例a不需要调用同步方法
	//但是需要赋值给data
	data = a;

	//输出0,10,20,30,40
	for (int i = 0; i < 5; i++)
	{
		std::cout << data[i] << "\n";
	}
}

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

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

int test_sharedMemory()
{
	// 一台计算机中可能有多块加速器,取默认加速器
	accelerator acc = accelerator(accelerator::default_accelerator);

	// 测试默认加速器是否支持共享内存
	if (!acc.supports_cpu_shared_memory)
	{
		std::cout << "The default accelerator does not support shared memory" << std::endl;
		return 1;
	}

	// 设置cpu默认存取方式
	acc.set_default_cpu_access_type(access_type_read_write);

	//为acc加速器建立accelerator_view(加速器视图)实例
	//读写方式默认为加速器default_cpu_access_type属性的设定
	accelerator_view acc_v = acc.default_view;

	// extent指示array实例建立一个含10个元素的一维数组
	extent<1> ex(10);

	// 指定加速器视图,输入数组在CPU上只写
	array<int, 1> arr_w(ex, acc_v, access_type_write);

	// 指定加速器视图,输出数组在CPU上只读
	array<int, 1> arr_r(ex, acc_v, access_type_read);

	// 指定加速器视图,可以在CPU上读写的数组
	array<int, 1> arr_rw(ex, acc_v, access_type_read_write);

	return 0;
}

index 类

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

void test_indexClass()
{
	int aCPP[] = { 1, 2, 3,
		4, 5, 6 };
	//新建2维(两行三列)array_view包装器
	array_view<int, 2> a(2, 3, aCPP);
	index<2> idx(1, 2);

	//输出6
	std::cout << a[idx] << "\n";
}

extent类

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

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

void test_extentClass()
{
	int aCPP[] = { 111, 112, 113, 114,
		           121, 122, 123, 124,
				   131, 132, 133, 134,

				   211, 212, 213, 214,
				   221, 222, 223, 224,
				   231, 232, 233, 234 };
	extent<3> e(2, 3, 4);
	array_view<int, 3> a(e, aCPP);

	//断言extent[0],[1],[2]的属性分别为2、3、4
	assert(2 == a.extent[0]);
	assert(3 == a.extent[1]);
	assert(4 == a.extent[2]);
}

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)中元素的平均值。

void test_tile()
{
	// 测试样本:
	int sampledata[] = {
		2, 2, 9, 7, 1, 4,
		4, 4, 8, 8, 3, 4,
		1, 5, 1, 2, 5, 2,
		6, 8, 3, 2, 7, 2 };

	// The tiles(下面是6个砖面):
	// 2 2    9 7    1 4
	// 4 4    8 8    3 4
	//
	// 1 5    1 2    5 2
	// 6 8    3 2    7 2

	// averagedata用来存放运算结果:
	int averagedata[] = {
		0, 0, 0, 0, 0, 0,
		0, 0, 0, 0, 0, 0,
		0, 0, 0, 0, 0, 0,
		0, 0, 0, 0, 0, 0,
	};

	//每四个元素(四根线程)组成一个tile(砖面),所以共有六个tile(砖面)
	array_view<int, 2> sample(4, 6, sampledata);
	array_view<int, 2> average(4, 6, averagedata);

	//通过[1]extent.tile代替extent[2]tiled_index代替index,启用平铺模式
	parallel_for_each(
		// 把extent切分为以2*2为单位的tile(砖面)
		sample.extent.tile<2, 2>(),
		[=](tiled_index<2, 2> idx) restrict(amp)
	{
		//tile_static关键字的变量范围是整个tile(砖面)
		//所以每个tile(砖面)(2*2=4根线程)只实例化一个tile_static
		tile_static int nums[2][2];

		//tile(砖面)中的所有线程分别运行下面的代码
		//把值复制到tile_static实例nums,所以同一个nums会被赋值2*2=4次
		nums[idx.local[1]][idx.local[0]] = sample[idx.global];

		//等待tile(砖面)中的所有线程,运行完上面这段代码
		idx.barrier.wait();

		//现在nums中的2*2=4个元素已经有有效值了
		//tile(砖面)中的所有线程再次分别运行下面的代码

		//计算平均值,
		int sum = nums[0][0] + nums[0][1] + nums[1][0] + nums[1][1];

		//把计算结果复制到array_view对象中.
		average[idx.global] = sum / 4;
	}
	);

	//打印运算结果
	for (int i = 0; i < 4; i++) {
		for (int j = 0; j < 6; j++) {
			std::cout << average(i, j) << " ";
		}
		std::cout << "\n";
	}

	// Output:
	// 3 3 8 8 3 3
	// 3 3 8 8 3 3
	// 5 5 2 2 4 4
	// 5 5 2 2 4 4
}

使用平铺的好处是,从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方法的调用提高了应用的性能。

// 使用tile_static内存屏障
parallel_for_each(matrix.extent.tile<SAMPLESIZE, SAMPLESIZE>(),
     [=, &averages](tiled_index<SAMPLESIZE, SAMPLESIZE> t_idx) restrict(amp)
{
    // 把数据从全局内存中复制到title_static内存中.
    tile_static floattileValues[SAMPLESIZE][SAMPLESIZE];
   tileValues[t_idx.local[0]][t_idx.local[1]] = matrix[t_idx];

    // 等待title_static内存中的数据复制完毕
   t_idx.barrier.wait_with_tile_static_memory_fence();

    // 如果你移除if语句,代码会把被tile(砖面)中的所有线程调用,这样每个
    // tile(砖面)中的元素都会被分配一个同样的平均值.
    if (t_idx.local[0] == 0&& t_idx.local[1] == 0) {
        for (int trow = 0; trow <SAMPLESIZE; trow++) {
            for (int tcol = 0; tcol< SAMPLESIZE; tcol++) {
               averages(t_idx.tile[0],t_idx.tile[1]) += tileValues[trow][tcol];
            }
        }
       averages(t_idx.tile[0],t_idx.tile[1]) /= (float) (SAMPLESIZE *SAMPLESIZE);
    }
});

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

时间: 2024-12-19 21:43:40

C++ AMP 介绍(二)的相关文章

linux程序分析工具介绍(二)—-ldd,nm

本文要介绍的ldd和nm是linux下,两个用来分析程序很实用的工具.ldd是用来分析程序运行时需要依赖的动态库的工具:nm是用来查看指定程序中的符号表相关内容的工具.下面通过例子,分别来介绍一下这两个工具: 1. ldd, 先看下面的例子, 用ldd查看cs程序所依赖的动态库: [email protected]:~/Public$ ldd cs linux-gate.so.1 => (0xffffe000) libz.so.1 => /lib/libz.so.1 (0xb7f8c000)

Avaya Exrience Portal(AEP)基础介绍 二(四大组件总结)

什么是Avaya Exrience Portal(AEP)? 1:提供语音(或者多媒体)自动化用户体验的软件平台:  2:基于标准的VXML(定义了如何使用语音识别.语音合成.互联网访问.数据库访问.语音文件播放.DTMF输入等功能开发一个完整的语音应用系统.)和CCXML(它基于XML定义了一系列开放标准的呼叫控制API)的应用:  3:支持多媒体处理:  4:完全基于Web架构:  5:支持TTS文语转换和语音识别技术:     2)AEP包含的组件   EPM:AEP集成的管理,报表,监控

Lucene.Net 2.3.1开发介绍 —— 二、分词(五)

原文:Lucene.Net 2.3.1开发介绍 -- 二.分词(五) 2.1.3 二元分词 上一节通过变换查询表达式满足了需求,但是在实际应用中,如果那样查询,会出现另外一个问题,因为,那样搜索,是只要出现这个字,不管它出现在什么位置.这就产生了上一小节开头讲的,对准确性产生了极大干扰.比如,如果有一段这样的话:“这是一个英雄!他有无法用词汇形容的孤单,但是他并没有用言语来表达.”这句话包含了“英 语 单 词”这四个字,但是却和“英语单词”一点关系都没有.首先想到的解决方法,就是把句子按词来划分

Lucene.Net 2.3.1开发介绍 —— 二、分词(四)

原文:Lucene.Net 2.3.1开发介绍 -- 二.分词(四) 2.1.2 可以使用的内置分词 简单的分词方式并不能满足需求.前文说过Lucene.Net内置分词中StandardAnalyzer分词还算比较实用(见1.1.2小节).StandardAnalyzer为什么能满足我们的部分需求,而它又有哪些不足呢?看分词的好坏还是要从效果说起.简单的说,在中英文混合的情况下,StandardAnalyzer会把英文按空格拆,而中文则按单字拆.因为中文是按单字拆,所以对分词的准确性起到了干扰,

Lucene.Net 2.3.1开发介绍 —— 二、分词(三)

原文:Lucene.Net 2.3.1开发介绍 -- 二.分词(三) 1.3 分词器结构 1.3.1 分词器整体结构 从1.2节的分析,终于做到了管中窥豹,现在在Lucene.Net项目中添加一个类关系图,把TokenStream和他的儿孙们统统拉上去,就能比较好的把握他们之间的关系. 图 1.3.1.1 如图1.3.1.1 就是他们的类关系图.看出如果要做一个分词器,最短的路,就是继承第二代,成为第三代.然后再写一个Analyzer的子类,专门用来做新分词器的适配器就好了.转换器.  呵呵,写

红黑树详细介绍二

删除 RB-TRANSPLANT(T,u,v)函数是将u子树用v来代替,在替换的时候分为了三种情况,如果u就是root结点则直接替换u,如果树里面还包含有其它结点,则将u的左右子树转移到v的左右子树上面. RB-TRANSPLANT(T,u,v) if u.p == T.nil T.root = v else if u == u.p.left u.p.left = v else u.p.right = v v.p = u.p 删除代码 RB-DELETE(T,z) y = z y-origina

c#基础介绍二

类 1 using System; 2 namespace bin 3 { 4 public class Doctor 5 { 6 public Doctor(){} 7 public Doctor(string name,byte age) 8 { 9 this._name=name; 10 this._age=age; 11 } 12 private string _name; 13 private byte _age; 14 public string Name 15 { 16 get{r

NoSQL介绍(二)

NoSQL介绍(二) redis介绍 Redis和Memcached类似,也属于k-v数据存储 Redis官网redis.io 支持更多value类型,除了和string外,还支持hash.list(链表).sets(集合)和sorted sets(有序信息) redis使用了两种文件格式:全量数据(RDB)和增量请求(aof).全量数据格式是把内存中的数据写入磁盘,便于下次读取文件进行加载.增量请求文件则是把内存中的数据序列化为操作请求,用于读取文件进行replay得到数据,这种类似于mysq

物体的三维识别与6D位姿估计:PPF系列论文介绍(二)——PPF-MEAM

作者:袁野 Date:2020-03-24 来源:物体的三维识别与6D位姿估计:PPF系列论文介绍(二)——PPF-MEAM 文章“Point Pair Feature-Based Pose Estimation with Multiple Edge Appearance Models (PPF-MEAM) for Robotic Bin Picking”2018年发表在<Sensors>,是近年来ppf方法的一个代表性的继承与发展.一.算法主要框架 算法分为线下训练阶段和线上匹配阶段.如下图

Android support library支持包常用控件介绍(二)

谷歌官方推出Material Design 设计理念已经有段时间了,为支持更方便的实现 Material Design设计效果,官方给出了Android support design library 支持库,让开发者更容易的实现材料设计的效果.顺便推荐官方的一个图标库:Material Icons 控件名称 NavigationView FloatingActionButton TextInputLayout Snackbar TabLayout AppBarLayout Coordinator