正如上面的评论员所指出的那样,斐波那契是在 GPU 上进行并行化的最糟糕的候选者之一。您在这里可以期望的最好的结果是使用同步原语来执行一个又一个操作并序列化所有线程!
C++ AMP 运行时不保证它在 GPU 上调度线程块的顺序。假设它决定首先安排计算系列上半部分的图块?较低的将永远不会运行,内核将挂起。在 C++ AMP中没有等价物,wait()
因此线程不会阻塞,运行时不会指示它应该换出阻塞的线程/切片并运行其他线程。您实际上是在while
使用wait
. 这在 CPU 或 GPU 上都是一个坏主意,因为您在内存被同时读取和写入时引入了竞争条件。
但是,您的问题在一般线程同步的上下文中是一个有效的问题。使用 C++ AMP,您基本上有两种机制;障碍和原子操作。
theData
以下示例显示了使用原子来计算数组中大于 0.999的随机数的数量。该数组使用 0.0 和 1.0 之间的随机数进行初始化,因此这个数字非常小。这意味着阻塞原子操作的成本很少发生。
array_view<float, 1> theDataView(int(theData.size()), theData);
int exceptionalOccurrences = 0;
array_view<int> count(1, &exceptionalOccurrences);
parallel_for_each(theDataView.extent, [=] (index<1> idx) restrict(amp)
{
if (theDataView[idx] >= 0.999f) // Exceptional occurrence.
{
atomic_fetch_inc(&count(0));
}
theDataView[idx] = // Update the value...
});
count.synchronize();
虽然这显示了如何使用原子,但这是使用(映射和)归约操作来完全避免使用原子的更有效的解决方案。
以下示例使用屏障来同步磁贴内的线程间的内存访问。Barrier之前的前半部分代码使用所有线程将数据加载到tile_static内存中,然后使用barrier使所有线程等待直到所有数据加载完毕才能访问数据。
array<float, 2> inData(1000, 1000);
array<float, 2> outData(1000, 1000);
parallel_for_each(view,
inData.extent.tile<tileSize, tileSize>(), [=, &inData, &outData]
(tiled_index<tileSize, tileSize> tidx) restrict(amp)
{
tile_static float localData[tileSize][tileSize];
localData[tidx.local[1]][tidx.local[0]] = inData[tidx.global];
tidx.barrier.wait();
index<2> outIdx(index<2>(tidx.tile_origin[1],
tidx.tile_origin[0]) + tidx.local);
outData[outIdx] = localData[tidx.local[0]][tidx.local[1]];
});
因此,原子使您能够在所有线程之间安全地共享内存,但代价是一些显着的同步开销。原子是非阻塞的。屏障允许您在一个 tile 内的线程之间同步执行和内存访问,它们是阻塞的。C++AMP 没有将两者结合起来的功能,您不能在 GPU 上的所有线程上阻塞。
这是编程模型的基本假设。所有线程都受到最小同步的影响,并且可以在线程之间很少或没有依赖关系的情况下运行。我的C++ AMP 书籍中关于优化和性能的章节中涵盖了大部分内容。