我需要找到有关统一着色器阵列如何访问 GPU 内存的信息,以了解如何有效地使用它。我的显卡架构图显示不清晰。
我需要使用 C++Amp 将大图像加载到 GPU 内存中并将其分成小块(如 4x4 像素)。每一块都应该用不同的线程计算。我不知道线程如何共享对图像的访问。
有什么方法可以使线程在访问图像时不会相互阻塞?也许他们有自己的内存可以独占访问?
或者也许对统一内存的访问太快了以至于我不应该关心它(但我不相信它)?这非常重要,因为我需要为每张图像计算大约 10k 个子集。
我需要找到有关统一着色器阵列如何访问 GPU 内存的信息,以了解如何有效地使用它。我的显卡架构图显示不清晰。
我需要使用 C++Amp 将大图像加载到 GPU 内存中并将其分成小块(如 4x4 像素)。每一块都应该用不同的线程计算。我不知道线程如何共享对图像的访问。
有什么方法可以使线程在访问图像时不会相互阻塞?也许他们有自己的内存可以独占访问?
或者也许对统一内存的访问太快了以至于我不应该关心它(但我不相信它)?这非常重要,因为我需要为每张图像计算大约 10k 个子集。
tile_static
对于 C++ AMP,您希望在开始卷积计算之前将 tile 中的每个线程使用的数据加载到内存中。因为每个线程访问的像素也被其他线程读取,这允许您从(慢)全局内存中对每个像素进行一次读取,并将其缓存在(快速)平铺静态内存中,以便所有后续读取更快。
您可以在此处查看卷积平铺的示例。该DetectEdgeTiled
方法加载它需要的所有数据和调用idx.barrier.wait()
,以确保所有线程已完成将数据写入平铺静态内存。然后它利用tile_static
内存执行边缘检测代码。示例中还有许多其他这种模式的示例。请注意,其中的加载代码DetectEdgeTiled
之所以复杂,只是因为它必须考虑正在写入当前图块的像素边缘周围的附加像素,并且本质上是一个展开的循环,因此它是长度。
我不确定您是否以正确的方式思考这个问题。这里有两个级别的分区。为了计算每个像素的新值,执行此工作的线程读取周围像素块。此外,线程块(瓦片)将较大的像素数据块加载到tile_static
内存中。然后,图块上的每个线程计算块内一个像素的结果。
void ApplyEdgeDetectionTiledHelper(const array<ArgbPackedPixel, 2>& srcFrame,
array<ArgbPackedPixel, 2>& destFrame)
{
tiled_extent<tileSize, tileSize> computeDomain = GetTiledExtent(srcFrame.extent);
parallel_for_each(computeDomain.tile<tileSize, tileSize>(), [=, &srcFrame, &destFrame, &orgFrame](tiled_index<tileSize, tileSize> idx) restrict(amp)
{
DetectEdgeTiled(idx, srcFrame, destFrame, orgFrame);
});
}
void DetectEdgeTiled(
tiled_index<tileSize, tileSize> idx,
const array<ArgbPackedPixel, 2>& srcFrame,
array<ArgbPackedPixel, 2>& destFrame) restrict(amp)
{
const UINT shift = imageBorderWidth / 2;
const UINT startHeight = 0;
const UINT startWidth = 0;
const UINT endHeight = srcFrame.extent[0];
const UINT endWidth = srcFrame.extent[1];
tile_static RgbPixel localSrc[tileSize + imageBorderWidth ]
[tileSize + imageBorderWidth];
const UINT global_idxY = idx.global[0];
const UINT global_idxX = idx.global[1];
const UINT local_idxY = idx.local[0];
const UINT local_idxX = idx.local[1];
const UINT local_idx_tsY = local_idxY + shift;
const UINT local_idx_tsX = local_idxX + shift;
// Copy image data to tile_static memory. The if clauses are required to deal with threads that own a
// pixel close to the edge of the tile and need to copy additional halo data.
// This pixel
index<2> gNew = index<2>(global_idxY, global_idxX);
localSrc[local_idx_tsY][local_idx_tsX] = UnpackPixel(srcFrame[gNew]);
// Left edge
if (local_idxX < shift)
{
index<2> gNew = index<2>(global_idxY, global_idxX - shift);
localSrc[local_idx_tsY][local_idx_tsX-shift] = UnpackPixel(srcFrame[gNew]);
}
// Right edge
// Top edge
// Bottom edge
// Top Left corner
// Bottom Left corner
// Bottom Right corner
// Top Right corner
// Synchronize all threads so that none of them start calculation before
// all data is copied onto the current tile.
idx.barrier.wait();
// Make sure that the thread is not referring to a border pixel
// for which the filter cannot be applied.
if ((global_idxY >= startHeight + 1 && global_idxY <= endHeight - 1) &&
(global_idxX >= startWidth + 1 && global_idxX <= endWidth - 1))
{
RgbPixel result = Convolution(localSrc, index<2>(local_idx_tsY, local_idx_tsX));
destFrame[index<2>(global_idxY, global_idxX)] = result;
}
}
这段代码取自 CodePlex,我去掉了很多真实的实现以使其更清晰。
您可以在 C++ AMP 中使用 WRT @sharpneli 的答案texture<>
来实现与 OpenCL 图像相同的结果。CodePlex 上也有一个例子。
在这种特殊情况下,您不必担心。只需使用 OpenCL 图像。GPU 非常擅长简单地读取图像(由于纹理)。但是,此方法需要将结果写入单独的映像,因为您无法在单个内核中从同一映像读取和写入。如果您可以一次性执行计算(无需迭代),您应该使用它。
另一种方法是将其作为普通内存缓冲区访问,将波前(同步运行的线程组)中的部分加载到本地内存(这个内存非常快),执行计算并将完整的最终结果在计算后写回统一内存. 如果您需要在计算时读取和写入同一图像的值,则应使用此方法。如果您不受内存限制,您仍然可以从纹理中读取原始值,然后在本地内存中迭代并将最终结果写入单独的图像中。
只有当它不是 const * restrict 并且多个线程读取相同的位置时,从统一内存中读取才会很慢。一般来说,如果后续线程 id 读取后续位置,它会相当快。但是,如果您的线程同时写入和读取统一内存,那么它会很慢。