任务中的OpenCL共享内存

时间:2013-11-18 04:05:39

标签: java c++ parallel-processing opencl jocl

我一直致力于创建基于GPU的康威生命游戏计划。如果你不熟悉它,这里是Wikipedia Page。我创建了一个版本,通过保持一个值数组来工作,其中0代表一个死区,1个是活的。然后,内核只是写入一个图像缓冲区数据数组,根据单元格数据绘制图像,然后检查每个单元格的邻居,以更新单元格数组,以便下次执行渲染。

然而,更快的方法代替单元格的值,如果是死的则为负数,如果是活的则为正数。该单元格的数量表示它具有的邻居数量加上一个(使零成为不可能的值,因为我们无法将0与-0区分开)。然而,这意味着当产生或杀死一个单元格时,我们必须相应地更新它的八个邻居的值。因此,与仅需要从相邻存储器槽读取的工作过程不同,该过程必须写入那些槽。这样做是不一致的,输出的数组无效。例如,单元格包含诸如14之类的数字,其表示13个邻居,这是不可能的值。代码是正确的,因为我在cpu上编写了相同的过程,它按预期工作。经过测试,我相信当任务试图同时写入内存时会出现延迟,从而导致某种写入错误。例如,在读取数组数据和设置数据更改的时间之间可能存在延迟,从而导致另一个任务的过程不正确。我已经尝试过使用信号量和障碍,但刚刚学习了OpenCL和并行处理,并且还没有完全掌握它们。内核如下。

int wrap(int val, int limit){
    int response = val;
    if(response<0){response+=limit;}
    if(response>=limit){response-=limit;}
    return response;
}

__kernel void optimizedModel(
        __global uint *output,
        int sizeX, int sizeY,
        __global uint *colorMap,
        __global uint *newCellMap,
        __global uint *historyBuffer
)
{
    // the x and y coordinates that currently being computed
    unsigned int x = get_global_id(0);
    unsigned int y = get_global_id(1);

    int cellValue = historyBuffer[sizeX*y+x];
    int neighborCount = abs(cellValue)-1;
    output[y*sizeX+x] = colorMap[cellValue > 0 ? 1 : 0];

    if(cellValue > 0){// if alive
        if(neighborCount < 2 || neighborCount > 3){
            // kill

            for(int i=-1; i<2; i++){
                for(int j=-1; j<2; j++){
                    if(i!=0 || j!=0){
                        int wxc = wrap(x+i, sizeX);
                        int wyc = wrap(y+j, sizeY);
                        newCellMap[sizeX*wyc+wxc] -= newCellMap[sizeX*wyc+wxc] > 0 ? 1 : -1;
                    }
                }
            }
            newCellMap[sizeX*y+x] *= -1;

            // end kill
        }
    }else{
        if(neighborCount==3){
            // spawn

            for(int i=-1; i<2; i++){
                for(int j=-1; j<2; j++){
                    if(i!=0 || j!=0){
                        int wxc = wrap(x+i, sizeX);
                        int wyc = wrap(y+j, sizeY);
                        newCellMap[sizeX*wyc+wxc] += newCellMap[sizeX*wyc+wxc] > 0 ? 1 : -1;
                    }
                }
            }
            newCellMap[sizeX*y+x] *= -1;

            // end spawn
        }
    }
}
  1. 数组输出是用于渲染的图像缓冲区数据 内核的计算。
  2. sizeX sizeY 常量分别是图像缓冲区的宽度和高度。
  3. colorMap 数组分别包含黑色和白色的rgb整数值,用于正确更改图像缓冲区的值以渲染颜色。
  4. newCellMap 数组是确定渲染后计算的更新单元格地图。
  5. historyBuffer 是内核调用开始时单元格的旧状态。每次执行内核时,此数组都会更新为newCellMap数组。
  6. 此外, wrap 功能使空间环形。我怎么能修复这个代码,使其按预期工作。为什么全局内存不会随任务的每次更改而更新?它不应该是共享内存吗?

2 个答案:

答案 0 :(得分:1)

正如sharpneli在他的回答中所说,你正在从不同的线程中读取和写入相同的内存区域,这会给出一个未定义的行为。

<强>解决方案: 您需要将newCellMap拆分为2个数组,一个用于上一次执行,另一个用于存储新值。然后,您需要在每次调用中更改主机端的内核参数,以便下一次迭代的oldvalues是前一次迭代的newvalues。由于您的算法结构化,您还需要在运行之前执行oldvaluesnewvalues的副本缓冲区。

__kernel void optimizedModel(
        __global uint *output,
        int sizeX, int sizeY,
        __global uint *colorMap,
        __global uint *oldCellMap,
        __global uint *newCellMap,
        __global uint *historyBuffer
)
{
    // the x and y coordinates that currently being computed
    unsigned int x = get_global_id(0);
    unsigned int y = get_global_id(1);

    int cellValue = historyBuffer[sizeX*y+x];
    int neighborCount = abs(cellValue)-1;
    output[y*sizeX+x] = colorMap[cellValue > 0 ? 1 : 0];

    if(cellValue > 0){// if alive
        if(neighborCount < 2 || neighborCount > 3){
            // kill

            for(int i=-1; i<2; i++){
                for(int j=-1; j<2; j++){
                    if(i!=0 || j!=0){
                        int wxc = wrap(x+i, sizeX);
                        int wyc = wrap(y+j, sizeY);
                        newCellMap[sizeX*wyc+wxc] -= oldCellMap[sizeX*wyc+wxc] > 0 ? 1 : -1;
                    }
                }
            }
            newCellMap[sizeX*y+x] *= -1;

            // end kill
        }
    }else{
        if(neighborCount==3){
            // spawn

            for(int i=-1; i<2; i++){
                for(int j=-1; j<2; j++){
                    if(i!=0 || j!=0){
                        int wxc = wrap(x+i, sizeX);
                        int wyc = wrap(y+j, sizeY);
                        newCellMap[sizeX*wyc+wxc] += oldCellMap[sizeX*wyc+wxc] > 0 ? 1 : -1;
                    }
                }
            }
            newCellMap[sizeX*y+x] *= -1;

            // end spawn
        }
    }
}

关于共享内存的问题有一个简单的答案。 OpenCL在HOST-DEVICE上没有共享内存

为设备创建内存缓冲区时,首先必须使用clEnqueueWriteBuffer()初始化该内存区域,然后使用clEnqueueWriteBuffer()进行读取以获取结果。即使您有指向内存区域的指针,您的指针也是指向该区域的主机端副本的指针。哪个可能没有最后版本的设备计算输出。

PD:我很久以前在OpenCL上创建了一个“实时”游戏,我发现更简单,更快捷的方法就是创建一个大的2D位数组(位寻址)。然后编写一段没有任何分支的代码,简单地分析neibours并获取该单元格的更新值。由于使用了位寻址,因此每个线程的读/写内存量远低于寻址字符/字符/其他内存。我在一台非常老的OpenCL HW(nVIDIA 9100M G)中达到了33Mcells / sec。只是为了让您知道您的if / else方法可能不是最有效的方法。

答案 1 :(得分:1)

作为参考,我让你在这里实现生命游戏(OpenCL内核):

//Each work-item processess one 4x2 block of cells, but needs to access to the (3x3)x(4x2) block of cells surrounding it
//    . . . . . .
//    . * * * * .
//    . * * * * .
//    . . . . . .

 __kernel void life (__global unsigned char * input, __global unsigned char * output){

    int x_length = get_global_size(0);
    int x_id = get_global_id(0);
    int y_length = get_global_size(1);
    int y_id = get_global_id(1);
    //int lx_length = get_local_size(0);
    //int ly_length = get_local_size(1);

    int x_n = (x_length+x_id-1)%x_length; //Negative X
    int x_p = (x_length+x_id+1)%x_length; //Positive X
    int y_n = (y_length+y_id-1)%y_length; //Negative Y
    int y_p = (y_length+y_id+1)%y_length; //Positive X

    //Get the data of the surrounding blocks (TODO: Make this shared across the local group)
    unsigned char block[3][3];
    block[0][0] = input[x_n + y_n*x_length];
    block[1][0] = input[x_id + y_n*x_length];
    block[2][0] = input[x_p + y_n*x_length];
    block[0][1] = input[x_n + y_id*x_length];
    block[1][1] = input[x_id + y_id*x_length];
    block[2][1] = input[x_p + y_id*x_length];
    block[0][2] = input[x_n + y_p*x_length];
    block[1][2] = input[x_id + y_p*x_length];
    block[2][2] = input[x_p + y_p*x_length];

    //Expand the block to points (bool array)
    bool point[6][4];
    point[0][0] = (bool)(block[0][0] & 1);
    point[1][0] = (bool)(block[1][0] & 8);
    point[2][0] = (bool)(block[1][0] & 4);
    point[3][0] = (bool)(block[1][0] & 2);
    point[4][0] = (bool)(block[1][0] & 1);
    point[5][0] = (bool)(block[2][0] & 8);
    point[0][1] = (bool)(block[0][1] & 16);
    point[1][1] = (bool)(block[1][1] & 128);
    point[2][1] = (bool)(block[1][1] & 64);
    point[3][1] = (bool)(block[1][1] & 32);
    point[4][1] = (bool)(block[1][1] & 16);
    point[5][1] = (bool)(block[2][1] & 128);
    point[0][2] = (bool)(block[0][1] & 1);
    point[1][2] = (bool)(block[1][1] & 8);
    point[2][2] = (bool)(block[1][1] & 4);
    point[3][2] = (bool)(block[1][1] & 2);
    point[4][2] = (bool)(block[1][1] & 1);
    point[5][2] = (bool)(block[2][1] & 8);
    point[0][3] = (bool)(block[0][2] & 16);
    point[1][3] = (bool)(block[1][2] & 128);
    point[2][3] = (bool)(block[1][2] & 64);
    point[3][3] = (bool)(block[1][2] & 32);
    point[4][3] = (bool)(block[1][2] & 16);
    point[5][3] = (bool)(block[2][2] & 128);

    //Process one point of the game of life!
    unsigned char out = (unsigned char)0;
    for(int j=0; j<2; j++){
        for(int i=0; i<4; i++){
            char num = point[i][j] + point[i+1][j] + point[i+2][j] + point[i][j+1] + point[i+2][j+1] + point[i][j+2] + point[i+1][j+2] + point[i+2][j+2];
            if(num == 3 || num == 2 && point[i+1][j+1] ){
                out |= (128>>(i+4*j));
            }
        }
    }
    output[x_id + y_id*x_length] = out; //Assign to the output the new cells value
};

这里你不保存任何中间状态,只保存最后的细胞状态(活/死)。它没有分支,所以它在这个过程中非常快。

相关问题