最近在做基于GPU的并行BitonicSort排序,中间用到了矩阵转置。觉得矩阵转置虽然简单,但一个好的矩阵转置优化却很好表达了GPU程序优化的几个基本要素。所以记录下。这里GPU接口还是用Directx11DirectCompute,然后为了便于着重算法重点,这里我们只讨论HLSL的代码。

    最简单的GPU矩阵转置大家应该可以想到了,就是把XY做一个对换。

//Matrix Transpose Code

StructuredBuffer<int> inData;

RWStructuredBuffer<int> outData;

[numthreads(TRANSPOSE_BLOCK_SIZE,TRANSPOSE_BLOCK_SIZE,1)]

void MatrixTranspose(uint3 Gid : SV_GroupID, 

                      uint3 DTid : SV_DispatchThreadID, 

                      uint3 GTid : SV_GroupThreadID, 

                      uint GI : SV_GroupIndex)

{

if(DTid.x<g_iWidth&&DTid.y<g_iHeight)

{

   uint2 XY = DTid.yx ;

        outData[DTid.x* g_iHeight + DTid.y] = inData[DTid.y*g_iWidth+DTid.x];

}

}

    但随机的读写对GPU来说效率很低,我们应该充分利用每个thread groupshared memory。对shared memory我的个人理解是每个thread group都有的一个专属高速cache,然后每个thread group只管自己的这块shared memory,对别的thread groupshared memory一无所知。thread线程对这块share memory的读写速度要比直接全局随机读写要快的多,所以我们应该好好利用shared momery来为我们的程序提速。

    如何利用shared memory呢。一句话就是把数据分成不同的相对独立(或者暂时相对独立)的小块,分别载入各个thread groupshared memory中。然后再分别处理整合。

    利用shared memory后的代码如下:

//Matrix Transpose Code

groupshared int transpose_shared_data[TRANSPOSE_BLOCK_SIZE*TRANSPOSE_BLOCK_SIZE];

[numthreads(TRANSPOSE_BLOCK_SIZE,TRANSPOSE_BLOCK_SIZE,1)]

void MatrixTranspose(uint3 Gid : SV_GroupID, 

                      uint3 DTid : SV_DispatchThreadID, 

                      uint3 GTid : SV_GroupThreadID, 

                      uint GI : SV_GroupIndex)

{

if(DTid.x<g_iWidth&&DTid.y<g_iHeight)

{

transpose_shared_data[GI]=inData[DTid.y*g_iWidth+DTid.x];

}

GroupMemoryBarrierWithGroupSync();

if(DTid.x<g_iWidth&&DTid.y<g_iHeight)

{

   uint2 XY = DTid.yx ;

      outData[XY.y * g_iHeight + XY.x] = transpose_shared_data[GTid.y * TRANSPOSE_BLOCK_SIZE + GTid.x];

}

}

    最后一个优化我大概知道是什么意思,但是细节我有点纠结,这个优化是是参考DirectxSDKComputeSort这个代码的。它意思是我们用高速的tranpose_shared_data的行,写入到慢速的随机读写显存outData的列。因为列数据在显存中是不连续的,那么写入不连续的数据就会比较慢,而且outData本来就慢,写入它不连续地址的显存就更加慢了。为此我们要用高速的tranpose_shared_data的列,写入到慢速的outData的行。

//Matrix Transpose Code

groupshared int transpose_shared_data[TRANSPOSE_BLOCK_SIZE*TRANSPOSE_BLOCK_SIZE];

[numthreads(TRANSPOSE_BLOCK_SIZE,TRANSPOSE_BLOCK_SIZE,1)]

void MatrixTranspose(uint3 Gid : SV_GroupID, 

                      uint3 DTid : SV_DispatchThreadID, 

                      uint3 GTid : SV_GroupThreadID, 

                      uint GI : SV_GroupIndex)

{

if(DTid.x<g_iWidth&&DTid.y<g_iHeight)

{

transpose_shared_data[GI]=inData[DTid.y*g_iWidth+DTid.x];

}

GroupMemoryBarrierWithGroupSync();

if(DTid.x<g_iWidth&&DTid.y<g_iHeight)

{

uint2 XY=DTid.yx+GTid.xy-GTid.yx;     

 outData[XY.y*g_iHeight+XY.x]=transpose_shared_data[GTid.x*TRANSPOSE_BLOCK_SIZE+GTid.y];

}

}

    注意加粗的uint2 XY=DTid.yx+GTid.xy-GTid.yx; 因为这里是用的transpose_shared_data的列,所以线程坐标和数据坐标是不一样了,所以要加上这么一个偏移。



    但是这个优化让我不明白的地方是既然所有的线程都是并发的,怎么会有所谓连续的问题呢。无论是用shared memory的行还是列,反正都是并行的,应该不存在所谓是行还是列,是否连续的问题啊。有点纠结。

posted on 2011-12-12 10:57  Bester  阅读(531)  评论(0编辑  收藏  举报