如何在C ++ AMP中相互同步线程?

时间:2013-10-22 14:35:38

标签: c++ c++-amp

我正在尝试使用C ++ AMP计算Fibonacci,每个数字将取决于其前两个。所以代码是这样的:

for (int i = 0; i < size; i++){
    a[i] = 0;
    if (i == 0 || i == 1) a[i] = 1;
}
array_view<int, 1> A(size, a);
parallel_for_each(
    A.extent,
    [=](index<1> idx)restrict(amp){
        if( A[idx] == 0 ){
             while (A[idx - 2] == 0); 
             while (A[idx - 1] == 0);
             A[idx] = A[idx - 1] + A[idx - 2];
        }
 });

第三个线程(idx [0] == 2)将在此行等待:

A[idx] = A[idx - 1] + A[idx - 2];

它不会被设置为非零数字,因此所有线程跟随只会卡在循环中。

有什么出路吗?

1 个答案:

答案 0 :(得分:2)

正如上面的评论者所指出的,Fibonacci是在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();

虽然这显示了如何使用原子,但这是使用(map和)缩减操作来解决这个问题的更有效的方法,以避免完全使用原子。

以下示例使用屏障来同步磁贴内线程之间的内存访问。屏障之前的代码的前半部分使用所有线程将数据加载到tile_static内存中,然后使用屏障使所有线程等到所有数据都被加载,然后才能访问数据。

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]];
    });

因此,atomics使您能够以牺牲一些重要的同步开销为代价,安全地跨所有线程共享内存。原子是非阻塞的。障碍允许您在磁贴内的线程之间同步执行和内存访问,它们是阻塞的。没有C ++ AMP的功能组合这两者,你不能阻止GPU上的所有线程。

这是编程模型的基本假设。所有线程都经历最小的同步,并且可以在线程之间很少或没有依赖性地运行。我C++ AMP book中的优化和效果章节中介绍了大部分内容。