当前位置: 首页 > news >正文

《C++ AMP:用Visual C++加速大规模并行计算》——3.5 array_view T,N

本节书摘来自异步社区出版社《C++ AMP:用Visual C++加速大规模并行计算》一书中的第3章,第3.5节,作者: 【美】Kate Gregory , Ade Miller,更多章节内容可以访问云栖社区“异步社区”公众号查看。

3.5 array_view< T,N >

C++ AMP:用Visual C++加速大规模并行计算
array表示的是加速器上的数据。我们既可以在构造array的同时就加载数据,也可以延迟加载数据。无论哪一种做法,在执行一些计算以后,都几乎肯定要把结果从数组传回CPU,这样我们才能在应用程序的其他部分中使用这些结果。

注意事项:
一些应用,例如在前一章里看到的NBody案例,并没有选择把数据复制回来。它们把数据留在GPU上,以此来渲染移动粒子云。但更常规的做法是先返回值,然后在应用程序的另一个部分使用或显示给用户看。
当然,我们只使用数组也能写出来有用的程序,但C++ AMP还提供了array_view,它支持的功能能让我们更方便地直接使用数组。对加速器来说,array_view就像是一个数组,但它免去了把数据复制到加速器或从加速器复制数据的麻烦。

array_viewarray之间的关系有点儿(尽管这么说有点儿不准确)像是引用和引用指向的对象之间的关系。就像引用那样,数组视图在创建的时候就要初始化。同样和引用一样,改变array_view也会(最终)改变其来源的数据。但是,反过来就不正确了:改变array_view来源处的数据并不会自动地改变array_view,因此我们要小心地处理这种情况。

array_view的创建方式有两种:从加速器的array创建,或者从CPU上的一系列数据(如`std::
vector)中创建。array_view`构造完成后,数据可以按需复制。例如,加速器上的parallel_
for_each开始使用array_view中的值时,这些值就会复制到加速器上。并行处理结束后,
array_view用完地,加速器array_view中的新值会被同步回向量中。

下述代码演示了这种方法。向量v在CPU内存中创建,初始化为{0,1,2,3,4}。接下来,array_view使用v中存储的数据初始化。直到parallel_for_each开始在GPU上执行时,数据才会复制到GPU内存中。lambda表达式会在GPU上执行,作用是将数组中的每个元素值翻倍。在访问CPU上改变的值之前,程序必须首先调用array_view上的synchronize()方法。

std::vector<int> v(5);
std::iota(v.begin(), v.end(), 0);
array_view<int, 1> av(5,v);
parallel_for_each(av.extent, [=](index<1> idx) restrict(amp)
{
   av(idx) = av(idx) * 2;
});
av.synchronize();```
在未来或者在某些加速器上将会有针对性的优化,例如只把那些发生改变的元素值复制回去,但我们不能对此抱有过多期望。如果运行时代码知道array_view没有发生改变,`synchronize()`方法就什么都不会做。

我们可以调用array的view_as()方法,把array_view中的整个数组包装起来,view_as()返回的即是array_view。或者,也可以使用array类的section()方法,传入原点值和范围值,
section()包装的只是array_view的一部分内容。有时候,我们会想换一种方式看数据;例如,把三维数组看成是一维数组。数组的reinterpret_as()方法会返回此类视图。就像标准C++的
reinterpret_cast,名字本身就能告诉阅读代码的人数据访问方式将会发生重大改变。这个方法会把数组的秩降为一,换言之,这个方法并不能以二维数组的形式来看待三维数组。元素布局与内存布局一样,最低有效索引值差值为1的元素彼此之间是相邻的。

一旦把std::vector这样的CPU受限数据包在array_view里,就最好不要改变CPU上的原始数据。因为缓存的存在,加速器所使用的array_view并不会发生改变,如果array_view复制回源向量,它们可能会被重写。如果必须要直接改变源数据,可以选择在array_view上调用refresh()刷新array_view改变。同步工作也是必须的,这样才能保证在源向量发生改变的同时,array_view不会改变。

C++11中的lambda表达式

最新的C++标准是C++11,引入了一些新的语言功能,其中就有lambda表达式。起先,lambda表达式好像只能解决一些小问题:它们把我们解放出来,免去我们在小函数传入std::sort()等标准算法之前先行命名的痛苦,也不需要我们构造仿函数(functor,也称作函子)或者函数对象把“做事”的想法从代码某处传入另一处。表面上,lambda表达式提供的只是“语法糖”,但是lambda表达式的方便性、可读性和简单性是很显然的,它们的存在使许多编程习语用起来更加趁手。C++ AMP的“入口”,即parallel_for_each,只有lambda表达式一个参数。下面我们快速回顾一下lambda表达式的语法,以方便先前对lambda表达式没有了解的读者。

任何可以使用表达式的地方都能使用lambda表达式,通常lambda表达式会出现在赋值语句的右侧,或者作为函数调用参数出现。例如,下述代码可以把某向量v的所有内部元素都返回到标准输出上:

void print(int i)
{
   std::wcout << i << " ";
}
// . . .
std::vector v(5, 0);
// . . .
std::for_each(v.begin(), v.end(), print);`
尽管在上面的代码里,print()函数和使用print()函数的代码离得很近,但多数情况下它们的距离肯定不会那么近,这样可读性就会受到影响。在大型代码库中,保持小型辅助函数命名唯一性往往不会那么容易。更糟的是,函数实体会随时发生变化,但函数并不会重新命名。诸如此类的限制会使许多开发者只想使用for,但这种做法并不好,因为for_each算法实际上比for的表达能力要强,for_each可以做更多复杂的事情。

使用lambda表达式作为for_each调用的最后一个参数,我们就能把代码放到使用它的地方,这样我们就能直接了解代码的功能。这样也免去了对单行函数命名的烦扰,可以保持函数名以及函数体的准确性。下述代码是for_each调用的lambda表达:

std::for_each(v.begin(), v.end(), [](int i) { std::wcout << i << " ";});
所有的lambda表达式都会以[]作为前缀,但是方括号可能不会像本例一样为空。这被称为“捕获子句(capture clause)”。接下来,圆括号里是lambda的参数。针对每个向量元素,`for_
each算法都会调用一次lambda表达式,并将元素值传入lambda`表达式。最后,大括号里面是lambda表达式的内容。lambda表达式不需要写在单独的一行当中,它的写法并没有限制。它甚至还可以包含一个或多个return语句,从自身返回。当lambda没有return语句,甚至整个lambda只有一条return语句时,编译器能够自动推断出返回类型。其他情况都需要指定返回值类型,如下所示:

std::vector<int> v;
// . . .
std::vector<double> dv;
transform(v.begin(), v.end(), back_inserter(dv), [](int n) -> double
{
   if (n % 2 == 0) {
     return n * n * n;
   } else {
     return n / 2.0;
   }
});```
C++ AMP的parallel_for_each使用的lambda没有返回值,因此没有上述注解,在这里指定完全是为了完整性的需要。

lambda的参数是通过调用for_each()这样的代码传入的。lambda创建后作用域内的值都可以访问。lambda创建的同时,编译器会生成匿名函数对象,lambda作用域的变量取值可以保存在函数对象成员变量中。我们可以在捕获子句中指定哪些值是按值传递的:

int x, y;
// . . .
std::for_each(v.begin(), v.end(), x, y
{
  if (n >= x && n <= y)
    std::wcout << n << " ";
});`
也可以指定哪些值是按引用传递的:

int x, y;
// . . . std::for_each(v.begin(), v.end(), [&x, &y](int& r)
{
  const int old = r;
  r *= 2;
  x = y;
  y = old;
});```
可以使用捕获子句[=]指示编译器按值传入lambda作用体,也可以使用捕获子句[&]指示编译器按引用传入。甚至还可以将这两个子句组合在一起使用:

process(0, numItems, =, &y
{
   //use various values from calling scope
   // any changes to y in the lambda will be reflected in calling scope
});`

我们无需掌握关于C++ AMP代码如何使用lambda表达式的全部信息。现在只需要了解{}就足够了。只要了解捕获子句、lambda参数和lambda表达式作用体就行了。

相关文章:

  • 《用友ERP-U8(V8.72)模拟实战----财务、供应链和生产制造》一1.4 系统管理注册和导入演示账套...
  • 《Unreal Engine 4蓝图可视化编程》一导读
  • 《Splunk智能运维实战》——3.8 使用散点图根据大小和响应时间标识离散的请求...
  • 模块与包
  • 生成模型和判别模型
  • 树莓派+pythonista实时监控系统
  • mysql开发之---使用游标双层嵌套对总表进行拆分为帖子表和回复表
  • window 下安装 wget 命令
  • V8 Ignition:JS 引擎与字节码的不解之缘
  • centos安装vsftp
  • 【zabbix系列】安装与加入host
  • 【Sets】使用Google Guava工程中Sets工具包,实现集合的并集/交集/补集/差集
  • JAVA多线程入门
  • 20145223 杨梦云 《网络对抗》 Web基础
  • ionic入门之数据绑定显示-1
  • [译]如何构建服务器端web组件,为何要构建?
  • 【腾讯Bugly干货分享】从0到1打造直播 App
  • ABAP的include关键字,Java的import, C的include和C4C ABSL 的import比较
  • in typeof instanceof ===这些运算符有什么作用
  • JavaScript DOM 10 - 滚动
  • JS正则表达式精简教程(JavaScript RegExp 对象)
  • node.js
  • Nodejs和JavaWeb协助开发
  • QQ浏览器x5内核的兼容性问题
  • ReactNativeweexDeviceOne对比
  • Redis的resp协议
  • SQLServer插入数据
  • TiDB 源码阅读系列文章(十)Chunk 和执行框架简介
  • VuePress 静态网站生成
  • WinRAR存在严重的安全漏洞影响5亿用户
  • yii2权限控制rbac之rule详细讲解
  • 大主子表关联的性能优化方法
  • 给初学者:JavaScript 中数组操作注意点
  • 通过调用文摘列表API获取文摘
  • ​queue --- 一个同步的队列类​
  • #Z2294. 打印树的直径
  • #我与Java虚拟机的故事#连载04:一本让自己没面子的书
  • (2)STM32单片机上位机
  • (4)事件处理——(7)简单事件(Simple events)
  • (二)七种元启发算法(DBO、LO、SWO、COA、LSO、KOA、GRO)求解无人机路径规划MATLAB
  • .NET 6 Mysql Canal (CDC 增量同步,捕获变更数据) 案例版
  • .NET Micro Framework初体验
  • .NET Standard 的管理策略
  • .NET 使用配置文件
  • .NET 应用启用与禁用自动生成绑定重定向 (bindingRedirect),解决不同版本 dll 的依赖问题
  • .NetCore部署微服务(二)
  • .net操作Excel出错解决
  • .NET关于 跳过SSL中遇到的问题
  • .NET企业级应用架构设计系列之技术选型
  • .net知识和学习方法系列(二十一)CLR-枚举
  • .php文件都打不开,打不开php文件怎么办
  • @AliasFor注解
  • @RequestBody详解:用于获取请求体中的Json格式参数
  • @RequestParam,@RequestBody和@PathVariable 区别
  • @Service注解让spring找到你的Service bean