コードがコンパイルされない理由は、sum
がクラス内で宣言されていて、 でラップされていないためarray_view
です。基本的にthis->sum
、AMP 制限付きコードからアクセスしようとしています。sum
に渡す前に、次を使用してラップする必要がありparallel_for_each
ますavSum
。
int sum = 0;
array_view<int, 1> avSum(1, &sum);
また、アトミック操作を使用して複数のスレッドにわたって の値をインクリメントする必要がありsum
、GPU によって提供される並列処理が大幅に無効になります。これは正しいアプローチではありません。
割引
あなたが達成しようとしていると思うのは削減です。入力配列のすべての値を合計して、単一の結果を返そうとしています。これは、GPU プログラミングで十分に文書化された問題です。NVidia は、これに関するいくつかのホワイト ペーパーを作成しています。C++ AMP Bookもこれについて詳しく説明しています。
これが最も単純な実装です。タイリングを使用せず、比較的非効率的ですが、理解しやすいです。ループの各反復はstride
、最終結果が要素 0 になるまで、配列の連続する要素を追加します。8 つの要素の配列の場合:
stride = 4: a[0] += a[4]; a[1] += a[5]; a[2] += a[6]; a[3] += a[7]
stride = 2: a[0] += a[2]; a[1] += a[1];
ゼロ要素に合計が含まれるようになりました。
class SimpleReduction
{
public:
int Reduce(accelerator_view& view, const std::vector<int>& source,
double& computeTime) const
{
assert(source.size() <= UINT_MAX);
int elementCount = static_cast<int>(source.size());
// Copy data
array<int, 1> a(elementCount, source.cbegin(), source.cend(), view);
std::vector<int> result(1);
int tailResult = (elementCount % 2) ? source[elementCount - 1] : 0;
array_view<int, 1> tailResultView(1, &tailResult);
for (int stride = (elementCount / 2); stride > 0; stride /= 2)
{
parallel_for_each(view, extent<1>(stride), [=, &a] (index<1> idx)
restrict(amp)
{
a[idx] += a[idx + stride];
// If there are an odd number of elements then the
// first thread adds the last element.
if ((idx[0] == 0) && (stride & 0x1) && (stride != 1))
tailResultView[idx] += a[stride - 1];
});
}
// Only copy out the first element in the array as this
// contains the final answer.
copy(a.section(0, 1), result.begin());
tailResultView.synchronize();
return result[0] + tailResult;
}
};
タイル内の各スレッドがその要素の結果を生成する責任を負い、すべてのタイルの結果が合計される場所にこれを並べて表示できます。
template <int TileSize>
class TiledReduction
{
public:
int Reduce(accelerator_view& view, const std::vector<int>& source,
double& computeTime) const
{
int elementCount = static_cast<int>(source.size());
// Copy data
array<int, 1> arr(elementCount, source.cbegin(), source.cend(), view);
int result;
computeTime = TimeFunc(view, [&]()
{
while (elementCount >= TileSize)
{
extent<1> e(elementCount);
array<int, 1> tmpArr(elementCount / TileSize);
parallel_for_each(view, e.tile<TileSize>(),
[=, &arr, &tmpArr] (tiled_index<TileSize> tidx) restrict(amp)
{
// For each tile do the reduction on the first thread of the tile.
// This isn't expected to be very efficient as all the other
// threads in the tile are idle.
if (tidx.local[0] == 0)
{
int tid = tidx.global[0];
int tempResult = arr[tid];
for (int i = 1; i < TileSize; ++i)
tempResult += arr[tid + i];
// Take the result from each tile and create a new array.
// This will be used in the next iteration. Use temporary
// array to avoid race condition.
tmpArr[tidx.tile[0]] = tempResult;
}
});
elementCount /= TileSize;
std::swap(tmpArr, arr);
}
// Copy the final results from each tile to the CPU and accumulate them
std::vector<int> partialResult(elementCount);
copy(arr.section(0, elementCount), partialResult.begin());
result = std::accumulate(partialResult.cbegin(), partialResult.cend(), 0);
});
return result;
}
};
これは、適切なメモリ アクセス パターンがないため、依然として最も効率的なソリューションではありません。この書籍の Codeplex サイトで、これに関するさらなる改良を確認できます。