现在的位置: 首页 > 综合 > 正文

C++ AMP: C++ AMP 平铺功能简介

2013年12月02日 ⁄ 综合 ⁄ 共 3432字 ⁄ 字号 评论关闭

C++ AMP 平铺功能简介

Daniel Moth

下载代码示例

本文介绍将随同 Visual Studio 11 一同发布的名为 C++ AMP 的预发布技术。 所有信息均有可能发生变更。

Visual Studio 11 为异构计算到 c + + 加速大规模并行 (c + + AMP) 通过主流带来支持。 我在这一问题,我认为必备阅读这篇文章中的另一篇文章中介绍了 c + + AMP。 所以如果你还没读,请这样做,p 上启动。 28.

我向你介绍 GPU 编程性能优化技术称为"拼贴"之前,请记住在上一篇文章中,您学习了索引 <N>、 <N> 程度、 < T、 N > array_view、 restrict(amp) 和 parallel_for_each。 你可以使用 c + + AMP API 实现您自己的数据的并行算法,如矩阵乘法在上一篇文章中共享和在这里重复图 1

图 1 矩阵乘法,简单的模型


1  void MatMul(vector<int>& vC, const vector<int>& vA,
     const vector<int>& vB, int M, int N, int W )
2  {
3    array_view<const int, 2> a(M, W, vA), b(W, N, vB);
4    array_view<int, 2> c(M, N, vC);
5    c.discard_data();
6    parallel_for_each(c.extent, [=](index<2> idx) restrict(amp)
7    {
8      int row = idx[0]; int col = idx[1];
9      int sum = 0;
10     for(int i = 0; i < b.extent[0]; i++)
11       sum += a(row, i) * b(i, col);
12     c[idx] = sum;
13   });
14   c.synchronize();
15 }
        

如果您不熟悉矩阵乘法,这里是一个在线的参考:bit.ly/HiuP

在本文中,我将向你介绍拼贴,Gpu 编程时,这是最常用的优化技术。 引入拼贴在你的算法的唯一原因是性能的额外级别,您可能可以实现通过重复使用数据和更好的内存访问模式。 拼贴允许您更好地利用 GPU 内存层次结构的较低级别比您可以用简单的模型,你知道从介绍性的文章。

有两个步骤,拼贴。 首先,您必须显式地平铺 (这一步时发生为你下盖上简单的模型) 的计算 ; 第二,你必须利用 tile_static 内存 (这第二个步骤并不为你自动发生,所以你要做这件事明确自己)。

tiled_extent 类

你知道那 parallel_for_each 接受程度对象作为其第一个参数。 程度描述计算域 — — 也就是说,多少线程 (大小) 和什么形状 (维度) 将执行计算。 考虑以下两种程度的示例:

extent<1> e(12);   // 12 threads in a single dimension
extent<2> ee(2,6); // 12 threads in two-dimensional space
        

有平铺到 parallel_for_each 接受 tiled_extent 参数的重载。 Tiled_extent 描述如何打破成同样大小的瓷砖的原始的程度。 您可以获取秩为只有三个高达的 tiled_extent。 如果您有比这更多的维度,您需要坚持做简单的模型或重构您的代码。 在平铺的线程的总数不能超过 1,024。

获取一个 tiled_extent 对象的最简单方法是通过程度,它返回 tiled_extent 为该范围内的对象上调用无参数的模板化瓦方法。 为早期程度上的两个例子中,您可以编写等),以获得相应的 tiled_extent 对象的下列:

tiled_extent<6> t_e = e.tile<6>();        // e.rank==t_e.rank
tiled_extent<2,2> t_ee = ee.tile<2, 2>(); // ee.rank==t_ee.rank
        

图形表示请参阅图 2,其中显示了如何平铺程度分区数据分成较小的子集,这在 c + + AMP 称为拼贴。

 
图 2 tiled_extent 是程度分为瓷砖

您选择作为必须在编译时已知的模板参数传递的数字。 他们必须均匀划分传递给 parallel_for_each 的全球范围内尺寸:

  • e [0] = 12 是整除 t_e.tile_extent[0]=6
  • ee [0] = 2 是整除 t_ee.tile_extent[0]=2
  • ee [1] = 6 是整除 t_ee.tile_extent[1]=2

出于性能原因,最小的拼贴大小在最少-­重大的尺寸应该为 16。 调整拼贴大小可以产生更好或更糟的性能结果,取决于您使用的硬件。 我的建议是,不要试图这样做 — — 相反,挑选一个数字,同样执行得好的一组广泛的硬件,从开始的这 16,甚至倍数。

tiled_index 类

你知道在 parallel_for_each 调用中传递在 lambda 与第二个参数作为您的代码。 您的代码接收索引的对象,并且你能想到的是索引的对象线程 id。例如:

parallel_for_each(my_extent,[=](index<2> idx) restrict(amp){
  my_array_view[idx] = ...
          });
        

当你平铺您传递给 parallel_for_each 的程度时,您传递中的 lambda 的签名接受 tiled_index。 Tiled_index 包含四个索引的对象。 例如,你仍然可以对您期待的 tiled_index 对象,按如下方式使用属性的索引对象:

parallel_for_each(my_extent.tile<2,2>(),[=](tiled_index<2,2> t_idx) restrict(amp){
  my_array_view[t_idx.global] = ...
          });
        

在编写时平铺的算法,你可能想知道本地索引到瓦 (不只是到整体计算域全球指数)。 您可以通过本地的 tiled_index 属性来获得该索引的对象。 对于某些算法是有用知道于计算中的其他拼贴瓷砖的索引和也的拼贴起源的全球指数。 您可以通过 tiled_index 的瓷砖和 tile_origin 属性来访问这些索引的对象。

使用前面的示例中的二维程度 (程度 <2> (2,6).tile <2,2> ()),你可以看到在图 3 突出显示广场的上述 tiled_index 属性的值。

 
图 3 tiled_index 属性返回索引对象

矩阵乘法 Tiled parallel_for_each 再探

图 1 你看到了一种使用 c + + AMP 的简单模型的矩阵乘法实现。 你如何改变这种算法所以可以显式地平与你们已经学到目前为止 (使用 tiled_extent 和 tiled_index)?

解决方案所示图 4,从较早前以粗体显示的列表的更改。

图 4 矩阵乘法、 Tiled,而不使用步骤 1 tile_static

3  array_view<const int, 2> a(M, W, vA), b(W, N, vB);
4  array_view<int, 2> c(M, N, vC);
5  c.discard_data();
6  parallel_for_each(c.extent.tile<16,16>(),
     [=](tiled_index<16,16> t_idx) restrict(amp)
7  {
8  int row = t_idx.global[0]; int col = t_idx.global[1];
9  int sum = 0;
10 for(int i = 0; i < b.extent[0]; i++)
11   sum += a(row, i) * b(i, col);
12 c[t_idx.global] = sum;
13 });
14 c.synchronize();
        

线上 6 我调用瓦法的程度,领料拼贴大小 (此示例中的 16 x 16),并更改接受 tiled_index 用匹配的拼贴大小模板参数 lambda。 Lambda 正文中我 t_idx.global (线 8 和 12) 替换所有 idx 出现。 这种机械论的转换是你应该做第一次为您所有的 c + + AMP 算法当您决定他们的平铺。 它是第一次 — — 但不是唯一的 — — 从简单的模型到平铺模型的旅程上的步骤。

注意到前面讨论的一件事是,这一变化,您需要确保拿均匀的拼贴大小将划分的全球范围内尺寸。 我的示例假定每个维度在哪里被 16 整除的方阵。 它也成静态的 const int 变量或模板参数吊出的拼贴大小通常的做法。

在简单的矩阵乘法样品中图 1,系统瓷砖代表在幕后您计算。 

【上篇】
【下篇】

抱歉!评论已关闭.