在OpenCL / C ++ Amp中访问GPU内存

时间:2014-01-07 17:01:00

标签: multithreading memory-management parallel-processing opencl c++-amp

我需要找到有关Unified Shader Array如何访问GPU内存以了解如何有效使用它的信息。我的显卡的架构图像并没有清楚地显示出来。

enter image description here

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

enter image description here

有没有办法以这样的方式做到这一点,即线程在访问图像时没有互相阻塞?也许他们有自己的记忆,可以独家访问?

enter image description here

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

2 个答案:

答案 0 :(得分:1)

对于C ++ AMP,您希望在开始卷积计算之前将平铺中每个线程使用的数据加载到tile_static内存中。因为每个线程访问也被其他线程读取的像素,所以这允许您对来自(慢)全局存储器的每个像素进行单次读取,并将其缓存在(快速)瓦片静态存储器中,以便所有后续读取更快。 / p>

您可以看到example of tiling for convolution hereDetectEdgeTiled方法加载它需要的所有数据和调用idx.barrier.wait(),以确保所有线程都已完成将数据写入tile静态内存。然后它利用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,我删除了许多真正的实现,以使其更清晰。

WRT @ sharpneli的回答你可以在C ++ AMP中使用texture<>来获得与OpenCL图像相同的结果。在CodePlex上还有一个例子。

答案 1 :(得分:0)

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

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

统一内存中的读取只有在不是const * restrict并且多个线程读取相同位置时才会很慢。通常,如果后续线程ID读取后续位置,则速度相当快。但是,如果您的线程都写入和读取统一内存,那么它将会很慢。