1

我是在 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?)

感谢所有的帮助!

4

2 回答 2

1

我有几个建议给你:

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

希望这可以帮助!

于 2012-04-30T06:44:01.083 回答
1

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

优化:

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

...

#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. px/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...
        }
    }
    
于 2012-05-02T15:11:06.860 回答