Java OpenCL如何在使用多个设备时重建缓冲区?

Java OpenCL如何在使用多个设备时重建缓冲区?,java,opencl,jocl,Java,Opencl,Jocl,我正在使用jogamp jocl库学习Java中的openCL。我的一项测试是绘制一张曼德布罗特地图。我有四个测试:简单的串行测试、使用Java executor接口的并行测试、针对单个设备的openCL测试和针对多个设备的openCL测试。前三个还行,后一个不行。当我比较多设备的(正确)输出和多设备解决方案的不正确输出时,我注意到颜色大致相同,但最后一个设备的输出是乱码的。我想我了解问题所在,但我无法解决它 问题是(imho)openCL使用向量缓冲区,我必须将输出转换成矩阵。我认为这个翻译是

我正在使用jogamp jocl库学习Java中的openCL。我的一项测试是绘制一张曼德布罗特地图。我有四个测试:简单的串行测试、使用Java executor接口的并行测试、针对单个设备的openCL测试和针对多个设备的openCL测试。前三个还行,后一个不行。当我比较多设备的(正确)输出和多设备解决方案的不正确输出时,我注意到颜色大致相同,但最后一个设备的输出是乱码的。我想我了解问题所在,但我无法解决它

问题是(imho)openCL使用向量缓冲区,我必须将输出转换成矩阵。我认为这个翻译是不正确的。我将mandelbrot映射划分为矩形,其中宽度(xSize)除以任务数,高度(ySize)保持不变,从而并行化代码。我认为我能够将这些信息正确地传输到内核中,但将其翻译回来是不正确的

  CLMultiContext mc = CLMultiContext.create (deviceList);
  try 
  {
     CLSimpleContextFactory factory = CLQueueContextFactory.createSimple (programSource);
     CLCommandQueuePool<CLSimpleQueueContext> pool = CLCommandQueuePool.create (factory, mc);
     IntBuffer dataC = Buffers.newDirectIntBuffer (xSize * ySize);
     IntBuffer subBufferC = null;
     int tasksPerQueue = 16;
     int taskCount = pool.getSize () * tasksPerQueue;
     int sliceWidth = xSize / taskCount;
     int sliceSize = sliceWidth * ySize;
     int bufferSize = sliceSize * taskCount;
     double sliceX = (pXMax - pXMin) / (double) taskCount;
     String kernelName = "Mandelbrot";

     out.println ("sliceSize: " + sliceSize);
     out.println ("sliceWidth: " + sliceWidth);
     out.println ("sS*h:" + sliceWidth * ySize);
     List<CLTestTask> tasks = new ArrayList<CLTestTask> (taskCount);

     for (int i = 0; i < taskCount; i++) 
     {
        subBufferC = Buffers.slice (dataC, i * sliceSize, sliceSize);
        tasks.add (new CLTestTask (kernelName, i, sliceWidth, xSize, ySize, maxIterations, 
              pXMin + i * sliceX, pYMin, xStep, yStep, subBufferC));
     } // for

     pool.invokeAll (tasks);

     // submit blocking immediately
     for (CLTestTask task: tasks) pool.submit (task).get ();

     // Ready read the buffer into the frequencies matrix
     // according to me this is the part that goes wrong
     int w = taskCount * sliceWidth;
     for (int tc = 0; tc < taskCount; tc++)
     {
        int offset = tc * sliceWidth;

        for (int y = 0; y < ySize; y++)
        {
           for (int x = offset; x < offset + sliceWidth; x++)
           {
              frequencies [y][x] = dataC.get (y * w + x);
           } // for
        } // for
     } // for

     pool.release();

您正在使用创建子缓冲区

subBufferC = Buffers.slice (dataC, i * sliceSize, sliceSize);
它们的内存数据如下所示:

0 1 3  10 11 12  19 20 21  28 29 30
4 5 6  13 14 15  22 23 24  31 32 33
7 8 9  16 17 18  25 26 27  34 35 36
通过使用opencl的矩形复制命令?如果是这样的话,那么您正在访问它们

output [iy * width + ix] = iteration;
因为
width
大于
sliceWidth
并写入内核中的越界

如果您不进行矩形复制或子缓冲区,而只是从原始缓冲区获取一个偏移量,那么它的内存布局如下

 0  1  3  4  5  6  7  8  9 | 10 11 12
 13 14 15 16 17 18|19 20 21  22 23 24
 25 26 27|28 29 30 31 32 33  34 35 36
因此,数组被访问/解释为倾斜或计算错误

您将偏移量作为内核的参数。但是,您也可以从内核排队参数中执行此操作。所以i和j将从它们的真值开始(而不是零),对于所有线程,您不需要在内核中将x0或y0添加到它们

我以前写过一个多设备api。它使用多个缓冲区,每个设备一个,它们的大小都与主缓冲区相同。它们只需将必要的部分(它们自己的区域)复制到主缓冲区(主机缓冲区)或从主缓冲区(主机缓冲区)复制到主缓冲区(主机缓冲区),这样所有设备的内核计算都保持完全相同,并使用适当的全局范围偏移。坏的一面是,所有设备上的主缓冲区实际上都是重复的。如果您有4个GPU和1GB数据,则总共需要4GB缓冲区。但通过这种方式,无论使用多少设备,内核成分都更容易阅读

如果您只为每个设备分配1/N大小的缓冲区(N个设备中的一个),那么您需要从子缓冲区的第0个地址复制到主缓冲区的
i*sliceHeight
,其中i是设备索引,考虑到数组是平面的,所以需要为每个设备使用opencl api的矩形缓冲区复制命令。我怀疑您也在使用平面数组,并且在内核中使用矩形副本和溢出。那么,我建议:

  • 从内核中删除任何与设备相关的偏移量和参数
  • 在内核排队参数中添加必要的偏移量,而不是参数
  • 如果尚未在每个设备上复制主缓冲区,请复制
  • 仅复制与设备相关的必要零件(如果是平面阵列分割,则为连续,用于二维解释/阵列分割的矩形副本)
如果整个数据无法放入设备中,您可以尝试映射/取消映射,这样它就不会在后台分配太多。它在网页上说:

多个命令队列可以映射一个区域或一个区域的重叠区域 用于读取的内存对象(即映射标志=CL\u映射读取)。内容 也可以读取为读取而映射的内存对象的所有区域 通过在设备上执行的内核。由用户进行写入的行为 在设备上执行到内存对象映射区域的内核是 未定义。映射(和取消映射)缓冲区或 用于写入的图像内存对象未定义

它并没有说,“读/写的非重叠映射是未定义的”,所以您可以在每个设备上为目标缓冲区上的并发读/写创建映射。但当与USE_HOST_PTR标志一起使用时(为了获得最大流性能),每个子缓冲区可能需要一个对齐的指针来开始,这可能会使将区域分割为适当的块变得更加困难。我对所有设备使用相同的整个数据数组,所以划分工作不是问题,因为我可以在对齐的缓冲区中映射或取消映射任何地址


以下是1-D分割的2设备结果(上部分由cpu,下部分由gpu):

这是内核的内部:

    unsigned ix = get_global_id (0)%w2;
     unsigned iy = get_global_id (0)/w2;

        if (ix >= w2) return;
        if (iy >= h2) return;

        double r = ix * 0.001;
        double i = iy * 0.001;

        double x = 0;
        double y = 0;

        double magnitudeSquared = 0;
        int iteration = 0;

        while (magnitudeSquared < 4 && iteration < 255) 
        {
            double x2 = x*x;
            double y2 = y*y;
            y = 2 * x * y + i;
            x = x2 - y2 + r;
            magnitudeSquared = x2+y2;
            iteration++;
        }

        b[(iy * w2 + ix)]   =(uchar4)(iteration/5.0,iteration/5.0,iteration/5.0,244);
unsigned ix=get_global_id(0)%w2;
无符号iy=get_global_id(0)/w2;
如果(ix>=w2)返回;
如果(iy>=h2)返回;
双r=ix*0.001;
双i=iy*0.001;
双x=0;
双y=0;
双震级平方=0;
int迭代=0;
而(震级平方<4&&迭代<255)
{
双x2=x*x;
双y2=y*y;
y=2*x*y+i;
x=x2-y2+r;
震级平方=x2+y2;
迭代++;
}
b[(iy*w2+ix)]=(uchar4)(迭代/5.0,迭代/5.0,迭代/5.0244);
对于512x512大小的图像(每个通道8位+alpha),使用FX8150(3.7GHz的7核)+700 MHz的R7_240需要17毫秒


此外,子缓冲区的大小与主机缓冲区的大小相同,这使得使用动态范围而不是静态范围(在异构设置、动态涡轮频率和打嗝/节流的情况下)来帮助实现动态负载平衡更快(无需重新分配)。结合“相同代码相同参数”的功能,它不会导致性能损失。例如,
c[i]=a[i]+b[i]
需要
c[i+i0]=a[i+i0]+b[i+i0]
在多个设备上工作,如果所有内核都从零开始,并且会增加更多的周期(除了内存瓶颈、可读性和分布c=a+b的怪异性)


你能把生成的图像放进去吗?唷!非常感谢您的回复!那个助教
 0  1  3  4  5  6  7  8  9 | 10 11 12
 13 14 15 16 17 18|19 20 21  22 23 24
 25 26 27|28 29 30 31 32 33  34 35 36
    unsigned ix = get_global_id (0)%w2;
     unsigned iy = get_global_id (0)/w2;

        if (ix >= w2) return;
        if (iy >= h2) return;

        double r = ix * 0.001;
        double i = iy * 0.001;

        double x = 0;
        double y = 0;

        double magnitudeSquared = 0;
        int iteration = 0;

        while (magnitudeSquared < 4 && iteration < 255) 
        {
            double x2 = x*x;
            double y2 = y*y;
            y = 2 * x * y + i;
            x = x2 - y2 + r;
            magnitudeSquared = x2+y2;
            iteration++;
        }

        b[(iy * w2 + ix)]   =(uchar4)(iteration/5.0,iteration/5.0,iteration/5.0,244);