【问题标题】:Using local workers in OpenCL for large matrix computation在 OpenCL 中使用本地工作人员进行大型矩阵计算
【发布时间】:2012-05-09 19:42:35
【问题描述】:

我是在 Visual Studio C# 中使用 OpenCL(带有 OpenCL.NET 库)的新手,目前正在开发一个计算大型 3D 矩阵的应用程序。在矩阵中的每个像素处,计算 192 个唯一值,然后求和以产生该像素的最终值。所以,在功能上,它就像一个 4-D 矩阵,(161 x 161 x 161) x 192。

现在我正在从我的主机代码中调用内核,如下所示:

//C# host code
...
float[] BigMatrix = new float[161*161*161]; //1-D result array
CLCalc.Program.Variable dev_BigMatrix = new CLCalc.Program.Variable(BigMatrix);
CLCalc.Program.Variable dev_OtherArray = new CLCalc.Program.Variable(otherArray);
//...load some other variables here too.
CLCalc.Program.Variable[] args = new CLCalc.Program.Variable[7] {//stuff...}

//Here, I execute the kernel, with a 2-dimensional worker pool:
BigMatrixCalc.Execute(args, new int[2]{N*N*N,192});
dev_BigMatrix.ReadFromDeviceTo(BigMatrix);

示例内核代码发布在下面。

__kernel void MyKernel(
__global float * BigMatrix
__global float * otherArray
//various other variables...
)
{
    int N = 161; //Size of matrix edges
    int pixel_id = get_global_id(0); //The location of the pixel in the 1D array
    int array_id = get_global_id(1); //The location within the otherArray


    //Finding the x,y,z values of the pixel_id.
    float3 p;
    p.x = pixel_id % N;    
    p.y = ((pixel_id % (N*N))-p.x)/N;
    p.z = (pixel_id - p.x - p.y*N)/(N*N);

    float result;

    //...
    //Some long calculation for 'result' involving otherArray and p...
    //...

    BigMatrix[pixel_id] += result;
}

我的代码目前可以工作,但是我正在寻找此应用程序的速度,我不确定我的工作人员/组设置是否是最佳方法(即工作人员池的尺寸为 161*161*161 和 192 )。

我已经看到了将全局工作池组织成本地工作组以提高效率的其他示例,但我不太确定如何在 OpenCL.NET 中实现它。我也不确定这与在工作池中创建另一个维度有何不同。

所以,我的问题是:我可以在这里使用本地组吗?如果可以,我将如何组织它们?一般来说,使用本地组与仅调用 n 维工作池有何不同? (即调用 Execute(args, new int[]{(N*N*N),192}),而不是本地工作组大小为 192?)

感谢大家的帮助!

【问题讨论】:

  • BigMatrix 中的值是否根据 BigMatrix 中的任何其他值计算得出?在计算中如何使用“p”?您能否提供有关您尝试进行的计算的更多信息?
  • 当然。计算中不使用 BigMatrix 的值,仅使用索引。 BigMatrix 的值最初为 0,并设置为计算结果。该计算使用 BigMatrix 中当前像素的索引 (p.x,p.y,p.z) 来找到指向由 otherArray 中的值指定的另一个点的向量。因此,每个计算都是唯一的,因为每个像素对于 otherArray 中的 192 个点中的每一个都有唯一的向量。该向量的大小和距离用于 BigMatrix 中最终值的最终计算。

标签: c# opencl gpu opencl.net


【解决方案1】:

我认为等待内存访问会损失很多性能。我已经回答了similar SO question. 我希望我的帖子能帮到你。如有任何问题,请提出。

优化:

  1. 我的内核版本的最大提升来自于将 otherArray 读入本地内存。
  2. 每个工作项在 BigMatrix 中计算 4 个值。这意味着它们可以同时写入同一缓存行。由于仍有超过 100 万个工作项要执行,因此并行度损失很小。

...

#define N 161
#define Nsqr N*N
#define Ncub N*N*N
#define otherSize 192

__kernel void MyKernel(__global float * BigMatrix, __global float * otherArray)
{
    //using 1 quarter of the total size of the matrix
    //this work item will be responsible for computing 4 consecutive values in BigMatrix
    //also reduces global size to (N^3)/4  ~= 1043000 for N=161

    int global_id = get_global_id(0) * 4; //The location of the first pixel in the 1D array
    int pixel_id;
    //array_id won't be used anymore. work items will process BigMatrix[pixel_id] entirely

    int local_id = get_local_id(0); //work item id within the group
    int local_size = get_local_size(0); //size of group


    float result[4]; //result cached for 4 global values
    int i, j;
    float3 p;

    //cache the values in otherArray to local memory
    //now each work item in the group will be able to read the values efficently
    //each element in otherArray will be read a total of N^3 times, so this is important
    //opencl specifies at least 16kb of local memory, so up to 4k floats will work fine
    __local float otherValues[otherSize];
    for(i=local_id; i<otherSize; i+= local_size){
        otherValues[i] = otherArray[i];
    }
    mem_fence(CLK_LOCAL_MEM_FENCE);

    //now this work item can compute the complete result for pixel_id 
    for(j=0;j<4;j++){
        result[j] = 0;
        pixel_id = global_id + j;

        //Finding the x,y,z values of the pixel_id.
        //TODO: optimize the calculation of p.y and p.z
        //they will be the same most of the time for a given work item
        p.x = pixel_id % N;    
        p.y = ((pixel_id % Nsqr)-p.x)/N;
        p.z = (pixel_id - p.x - p.y*N)/Nsqr;

        for(i=0;i<otherSize;i++){
            //...
            //Some long calculation for 'result' involving otherValues[i] and p...
            //...
            //result[j] += ...
        }
    }
    //4 consecutive writes to BigMatrix will fall in the same cacheline (faster)
    BigMatrix[global_id] += result[0];
    BigMatrix[global_id + 1] += result[1];
    BigMatrix[global_id + 2] += result[2];
    BigMatrix[global_id + 3] += result[3];
}

注意事项:

  1. 全局工作大小必须是四的倍数。理想情况下,4*workgroupsize 的倍数。这是因为没有错误检查来查看每个 pixel_id 是否在范围内:0..N^3-1。在等待内核执行时,CPU 可能会处理未处理的元素。
  2. 工作组规模应该相当大。这将迫使缓存值被更多地使用,并且在 LDS 中缓存数据的好处将会增加。
  3. p.x/y/z 的计算需要进一步优化,以避免过多昂贵的除法和模运算。请参阅下面的代码。

    __kernel void MyKernel(__global float * BigMatrix, __global float * otherArray)   {
    int global_id = get_global_id(0) * 4; //The location of the first pixel in the 1D array
    int pixel_id = global_id;
    
    int local_id = get_local_id(0); //work item id within the group
    int local_size = get_local_size(0); //size of group
    
    
    float result[4]; //result cached for 4 global values
    int i, j;
    float3 p;
    //Finding the initial x,y,z values of the pixel_id.
    p.x = pixel_id % N;    
    p.y = ((pixel_id % Nsqr)-p.x)/N;
    p.z = (pixel_id - p.x - p.y*N)/Nsqr;
    
    //cache the values here. same as above...
    
    //now this work item can compute the complete result for pixel_id 
    for(j=0;j<4;j++){
        result[j] = 0;
    //increment the x,y,and z values instead of computing them all from scratch
        p.x += 1;
        if(p.x >= N){
            p.x = 0;
            p.y += 1;
            if(p.y >= N){
                p.y = 0;
                p.z += 1;
            }
        }
    
        for(i=0;i<otherSize;i++){
            //same i loop as above...
        }
    }
    

【讨论】:

  • 感谢您的精彩回复!但是我确实有问题,因为我似乎无法让您的设置与我的代码一起使用:1)查看您的代码,每个工作线程都会创建一个新的缓存“otherValues”矩阵,但我不明白为什么大小缓存数组的仍然是 192...你不是只填充 (192/local_size) 元素吗?我认为其余元素将为空,对吗? 2) 同样,如果只有一些值可用,为什么要遍历最终 for 循环中的所有 192 个元素?我想我对缓存在本地和全局工作者方面的真正作用感到困惑。
  • 192 个浮点数的本地数组被创建并在整个工作组之间共享。复制数据的 for 循环从“local_id”开始,这对于组中的每个工作项都是不同的。然后它使用 i+=local_size 循环,以涵盖一个组中的工作项少于 192 个的情况。因此,如果您的工作组大小为 192,则每个工作项将准确地将一个元素复制到 otherValues 中。 mem_fence 行让组等到所有值都被复制后,才进入所有 192 个元素的计算循环。
  • otherValues 不能与 result[4] 混淆。 otherValues 在组中的所有工作项之间共享。 result 是每个工作项创建的私有数组,用于同时存储其四个结果,其唯一目的是延迟全局写入操作,直到 4 个连续的浮点数准备好写入。
【解决方案2】:

我有几个建议给你:

  1. 我认为您的代码存在竞争条件。您的最后一行代码与 BigMatrix 的相同元素被多个不同的工作项修改。
  2. 如果您的矩阵真的是 161x161x161,那么这里有很多工作项可以将这些尺寸用作您的唯一尺寸。您已经拥有超过 400 万个工作项,这对于您的机器来说应该是足够的并行度了。你不需要192倍。此外,如果您不将单个像素的计算拆分为多个工作项,则无需同步最终添加。
  3. 如果您的全局工作大小不是 2 的大幂的倍数,您可以尝试将其填充为 1。即使您将 NULL 作为本地工作大小传递,一些 OpenCL 实现也会为不能很好划分的全局大小选择低效的本地大小。
  4. 如果您的算法不需要本地内存或屏障,您几乎可以跳过本地工作组。

希望这会有所帮助!

【讨论】:

  • 感谢您的回复。我喜欢使用 atomic_add 的想法,但它似乎只适用于 int 类型。我的计算必须是浮点计算,所以我需要能够进行涉及浮点数的同步添加。有没有可以添加浮点数的 atomic_add 替代方法?
  • 呃。接得好。不,OpenCL 中不支持浮点原子。鉴于此,我真的会考虑只发布 161x161x161 的工作项。
  • #2 我同意。展开 192 循环有点矫枉过正。 #3 或者,计算最大的轮全局工作大小,并将剩余的工作分配给 CPU 内核。 #4我不同意这一点。我将发布我的解决方案;它依靠当地人来大大加快速度。
  • 我不否认使用本地人可以使这更快。我的声明 #4 说,如果您不使用本地人或障碍,请不要为工作组担心太多。
  • 对。我想这不是必需的。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2015-04-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多