【问题标题】:Accessing GPU memory in OpenCL/C++Amp在 OpenCL/C++Amp 中访问 GPU 内存
【发布时间】:2014-01-07 17:00:59
【问题描述】:

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

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

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

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

【问题讨论】:

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


    【解决方案1】:

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

    您可以看到example of tiling for convolution hereDetectEdgeTiled 方法加载它需要的所有数据并调用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,我去掉了很多真实的实现以使其更清晰。

    WRT @sharpneli 的回答,您可以在 C++ AMP 中使用 texture&lt;&gt; 来实现与 OpenCL 图像相同的结果。 CodePlex 上也有一个例子。

    【讨论】:

    • 但是当使用 OpenCL texture&lt;&gt; 时,我将如何对切片/块进行分区,以便它们也可以缓存部分图像?至于你的例子,我并不完全清楚移位和偏移是如何工作的。
    • 如果有帮助,这里还有另一个例子blogs.msdn.com/b/nativeconcurrency/archive/2011/11/01/…
    【解决方案2】:

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

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

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

    【讨论】:

    • 我想尽快完成。我想写入与源图像不同的输出图像。据我了解,您建议我应该将每个子集存储在不同的位置。我不完全理解您通过将波前(同步运行的线程组)中的部分加载到本地内存中来表达什么。你能说更多吗?也许一张图片展示了这个解决方案?
    • 这个页面以二维卷积的形式给出了一个很好的例子。 cmsoft.com.br/… 。它也有一些解释这个概念的图像。基本上每组线程都有一堆可用的内存,与全局内存相比,这非常快。所以它被用作用户管理的缓存。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2018-09-07
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多