Java OpenCL如何在使用多个设备时重建缓冲区?
我正在使用jogamp jocl库学习Java中的openCL。我的一项测试是绘制一张曼德布罗特地图。我有四个测试:简单的串行测试、使用Java executor接口的并行测试、针对单个设备的openCL测试和针对多个设备的openCL测试。前三个还行,后一个不行。当我比较多设备的(正确)输出和多设备解决方案的不正确输出时,我注意到颜色大致相同,但最后一个设备的输出是乱码的。我想我了解问题所在,但我无法解决它 问题是(imho)openCL使用向量缓冲区,我必须将输出转换成矩阵。我认为这个翻译是不正确的。我将mandelbrot映射划分为矩形,其中宽度(xSize)除以任务数,高度(ySize)保持不变,从而并行化代码。我认为我能够将这些信息正确地传输到内核中,但将其翻译回来是不正确的Java OpenCL如何在使用多个设备时重建缓冲区?,java,opencl,jocl,Java,Opencl,Jocl,我正在使用jogamp jocl库学习Java中的openCL。我的一项测试是绘制一张曼德布罗特地图。我有四个测试:简单的串行测试、使用Java executor接口的并行测试、针对单个设备的openCL测试和针对多个设备的openCL测试。前三个还行,后一个不行。当我比较多设备的(正确)输出和多设备解决方案的不正确输出时,我注意到颜色大致相同,但最后一个设备的输出是乱码的。我想我了解问题所在,但我无法解决它 问题是(imho)openCL使用向量缓冲区,我必须将输出转换成矩阵。我认为这个翻译是
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的矩形缓冲区复制命令。我怀疑您也在使用平面数组,并且在内核中使用矩形副本和溢出。那么,我建议:
- 从内核中删除任何与设备相关的偏移量和参数
- 在内核排队参数中添加必要的偏移量,而不是参数
- 如果尚未在每个设备上复制主缓冲区,请复制
- 仅复制与设备相关的必要零件(如果是平面阵列分割,则为连续,用于二维解释/阵列分割的矩形副本)
以下是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);