1

我需要找到关于统一着色器阵列如何访问GPU内存以了解如何有效使用它的信息。我的显卡架构的图像没有清楚地显示出来。在OpenCL/C++ Amp中访问GPU内存

enter image description here

我需要使用C++安培到一个大的图像加载到GPU存储器,并将其分成小块(如4×4像素)。每件作品都应该使用不同的线程进行计算。我不知道这些线程是如何分享对图像的访问权限的。

enter image description here

是否有这样的方式,线程不阻止对方在访问图像做的方法吗?也许他们有自己的记忆,可以独占访问?

enter image description here

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

回答

1

对于C++ AMP,您希望在开始卷积计算之前将瓦片中每个线程使用的数据加载到tile_static内存中。由于每个线程都访问其他线程也读取的像素,因此您可以对来自(慢速)全局内存的每个像素执行一次读取,并将其缓存到(快速)tile静态内存中,以便后续读取速度更快。你可以看到一个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<>来达到与OpenCL图像相同的效果。 CodePlex上还有一个例子。

+0

但是,当使用OpenCL纹理<>'我将如何分割瓷砖/块,以便它们也可以缓存部分图像?至于你的例子,它并没有完全清楚我的变化和偏移量是如何工作的。 –

+1

还有另一个例子,如果这有助于http://blogs.msdn.com/b/nativeconcurrency/archive/2011/11/01/convolution-sample.aspx –

0

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

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

只有当它不是const * restrict且多线程读取同一位置时,从统一内存中读取数据才会很慢。一般来说,如果后续的线程ID读取后续的位置,它是相当快的。但是,如果你的线程既能写入又能读取到统一的内存,那么速度会很慢。

+0

我想尽快做到这一点。我想写入与源图像不同的输出图像。据我了解,你建议我应该将每个子集存储在不同的地方。我并不完全理解你将**加载到波前(同步运行的线程组)到本地内存**中的部分**。你能再说一些吗?也许一个图像呈现这个解决方案 –

+0

这个页面给出了2d卷积形式的一个很好的例子。 http://www.cmsoft.com.br/index.php?option=com_content&view=category&layout=blog&id=142&Itemid=201。它也没有解释这个概念的图像。基本上每组线程都有一堆可用的内存,与全局内存相比,它非常快。所以它被用作用户管理的缓存。 – sharpneli