1

我需要找到有关统一着色器阵列如何访问 GPU 内存的信息,以了解如何有效地使用它。我的显卡架构图显示不清晰。

在此处输入图像描述

我需要使用 C++Amp 将大图像加载到 GPU 内存中并将其分成小块(如 4x4 像素)。每一块都应该用不同的线程计算。我不知道线程如何共享对图像的访问。

在此处输入图像描述

有什么方法可以使线程在访问图像时不会相互阻塞?也许他们有自己的内存可以独占访问?

在此处输入图像描述

或者也许对统一内存的访问太快了以至于我不应该关心它(但我不相信它)?这非常重要,因为我需要为每张图像计算大约 10k 个子集。

4

2 回答 2

1

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 上也有一个例子。

于 2014-01-08T18:16:40.027 回答
0

在这种特殊情况下,您不必担心。只需使用 OpenCL 图像。GPU 非常擅长简单地读取图像(由于纹理)。但是,此方法需要将结果写入单独的映像,因为您无法在单个内核中从同一映像读取和写入。如果您可以一次性执行计算(无需迭代),您应该使用它。

另一种方法是将其作为普通内存缓冲区访问,将波前(同步运行的线程组)中的部分加载到本地内存(这个内存非常快),执行计算并将完整的最终结果在计算后写回统一内存. 如果您需要在计算时读取和写入同一图像的值,则应使用此方法。如果您不受内存限制,您仍然可以从纹理中读取原始值,然后在本地内存中迭代并将最终结果写入单独的图像中。

只有当它不是 const * restrict 并且多个线程读取相同的位置时,从统一内存中读取才会很慢。一般来说,如果后续线程 id 读取后续位置,它会相当快。但是,如果您的线程同时写入和读取统一内存,那么它会很慢。

于 2014-01-07T19:34:31.250 回答