2

我目前正在平铺我的 c++ AMP 代码。对于每个图块,我有 4096 字节的数据经常被读取,所以我想将其声明为 tile_static。将其分成多个图块是不切实际的,因为每个线程都需要访问所有数据。我的瓦片由 128 个线程组成,因此它们应该在 Nvidia/AMD GPU 上占用 2-4 个扭曲。

我刚刚阅读了以下文章,这似乎表明我只能在每个 warp 的 tile_static 中使用 1024 位:http: //blogs.msdn.com/b/nativeconcurrency/archive/2012/08/14/avoid-bank-conflicts -on-tile-static-memory-with-c-amp.aspx

在一些现代 GPU 上,tile_static 内存由可以同时访问的“n”个大小相等的内存条组成,并且连续的“m”位字被映射到连续的内存条。tile_static 内存(即 n 和 m)的确切组织取决于硬件。例如,在 Nvidia GTX 580 卡或 ATI HD 5870 卡上,tile_static 内存具有32 个存储库 (n = 32),这些存储库的组织方式使得连续的 32 位字 (m = 32) 映射到连续的存储库。请注意,n 可能因硬件而异,m 通常为 32。在本文的其余部分,我将假设 m 为 32。

这是否意味着我可以为每个扭曲或每个线程声明多达 1024 位?所有 tile_static 变量是否在 warp 之间共享,或者每个 warp 都有自己的副本?

这些问题中有多少是依赖于硬件的,如果是,我怎样才能在运行时找出限制?

我已经阅读了一本 c++ AMP 书籍封面,虽然我很感谢作者向我介绍了这个主题,但它似乎没有解决这个问题(或者如果它解决了,我不明白它)。

网上有很多关于如何使用 tile_static 内存的信息(这是一个好的开始:http ://www.danielmoth.com/Blog/tilestatic-Tilebarrier-And-Tiled-Matrix-Multiplication-With-C-AMP .aspx),但似乎没有人谈论我们可以声明多少,因此不可能真正实现这些东西!最后一个链接给出了以下示例:

01: void MatrixMultiplyTiled(vector<float>& vC, 
         const vector<float>& vA, 
         const vector<float>& vB, int M, int N, int W)
02: {
03:   static const int TS = 16;

04:   array_view<const float,2> a(M, W, vA);
05:   array_view<const float,2> b(W, N, vB);
06:   array_view<float,2> c(M,N,vC); c.discard_data();

07:   parallel_for_each(c.extent.tile< TS, TS >(),
08:   [=] (tiled_index< TS, TS> t_idx) restrict(amp) 
09:   {
10:     int row = t_idx.local[0]; int col = t_idx.local[1];
11:     float sum = 0.0f;

12:     for (int i = 0; i < W; i += TS) {
13:        tile_static float locA[TS][TS], locB[TS][TS];
14:        locA[row][col] = a(t_idx.global[0], col + i);
15:        locB[row][col] = b(row + i, t_idx.global[1]);
16:        t_idx.barrier.wait();

17:        for (int k = 0; k < TS; k++)
18:          sum += locA[row][k] * locB[k][col];

19:        t_idx.barrier.wait();
20:     }

21:     c[t_idx.global] = sum;
22:   });
23: }

请注意,第 13 行声明了 2x 1024 位,这让我希望我的 4096 位不会要求太多...... - 我想这些问题更多地依赖于硬件/实现,而不是 AMP 语言扩展本身......

4

2 回答 2

3

首先,我认为您的意思是 2 x 1024 字节而不是位。瓦片静态内存是按瓦片声明的,而不是按线程或每个扭曲声明的。warp 实际上只是一种调度结构,用于组织一起执行的线程组,通常以 32 或 64 个为一组,具体取决于架构。为了使调度程序的工作更轻松,您应该使用包含多个线程的切片,这些线程的数量是经线大小的精确倍数。

在详细讨论这个之前,回顾一下 GPU 如何执行构成内核的线程会很有帮助。GPU 由多个处理器组成。AMD 将它们称为计算单元,而 NVIDIA 将它们称为流式多处理器。每个 CU 调度在称为 warp 的线程块或线程束中工作。当一个warp被阻塞时,CU调度程序可以通过切换到另一个warp而不是等待当前的warp来隐藏延迟。CU 能够使用这种方法来隐藏与内存访问相关的延迟,前提是有足够的 warp 可用。

本书未对此进行详细介绍的原因之一是因为 C++ AMP 设计为与硬件无关,并且在 DirectX 之上运行。因此,如果您在设计应用程序时考虑到特定的 GPU 细节,它的可移植性可能会降低。此外,由于在某些情况下 C++ AMP 是在 DX11 之上实现的,因此根本无法获得特定于硬件的信息。warp 大小、tile_static内存大小和缓存大小都是这方面的例子。与任何书籍一样,我们也有空间限制和出版期限。

但是,您可以对 warp 大小和 tile 内存做出合理的假设。在现代 GPU 上,假设 warp 大小为 32 或 64,平铺静态内存约为 10 KB。如果您真的想针对特定处理器调整代码,那么您可以使用制造商规范和/或显示适当详细信息的工具。

在 Tesla 上,共享内存为 16KB。在 Fermi 上,共享内存实际上是 64KB,并且可以配置为 48KB 软件管理的数据缓存和 16KB 硬件数据缓存,或者相反(16KB SW,48KB HW 缓存)。

10s 的 KB 看起来对于tile_static数组来说并不是很多内存,但实际上还有其他压力也会决定 tile 的大小;注册表压力之一。您还应该记住,一些非常大的图块通常会导致占用率低,从而导致代码效率低下。

我同意整个记忆库术语令人困惑。32 位是指内存条的大小,而不是总内存的大小。您可以将银行视为访问机制,而不是总存储。正如上面提到的,每个 32 位存储体都映射到连续的 32 位内存地址。因为每个周期您可以访问一个存储库,所以访问内存的最有效方法是从每个存储库中读取一项。或者,让所有线程读取相同的项目(广播)。本书在性能/优化一章中对此进行了一些讨论。

于 2013-11-12T04:53:47.900 回答
0

大约32kb。如果你达到了限制,当你尝试编译时你会得到一个错误。

如果你没有收到错误,那你就没事。您应该能够通过声明一个庞大的tile_static数组来自己测试它,并且您应该收到一条愤怒的消息,告诉您tile_static限制是什么。

于 2015-09-28T21:05:56.183 回答