我正面临OpenCL的问题,我希望有人会暗示原因可能是什么。以下是该程序的一个版本,简化为问题。我有一个大小为4000的输入int数组。在我的内核中,我正在进行扫描。显然,有很好的方法可以并行执行此操作,但为了重现问题,只有一个线程正在执行整个计算。在扫描之前,输入(result_mask)只有值0或1.
__kernel void
sel_a(__global db_tuple * input,
__global int * result_mask,
__global int * result_count,
const unsigned int max_id)
{
// update mask based on input in parallel
mem_fence(CLK_GLOBAL_MEM_FENCE);
if(gid == 0)
{
int i, c = 0;
for(i = 0; i < max_id; i++)
{
if(result_mask[i]!=0)
{
c++;
result_mask[i] = 5;
}
else
{
result_mask[i] = 5;
}
}
*result_count = c;
}
}
预期结果将是最初具有不同于0的值的元素数量,而结果掩码中只有5个元素。但事实并非如此。输出如下:
...
5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5
5 5 5 5 5 5 5 5 5 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 1 0 0 0 1 0 0 0 1 0 0 0 0 0 0 0 0 0 0
0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 5 5 5 5 5 5 5 5 5 5 5
5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5
...
我在大约一段时间后得到这个80个元素的块。 3200个元素。它并不总是相同的位置,但它总是相同数量的元素 - 80.它甚至更奇怪 - 如果我将第一行更改为 if(gid == 2000) 问题消失了。但是,在玩了线程ID之后,我得出的结论是问题没有消失,它只是移动了。使用线程1425,我得到问题的一半时间,当我得到它时,buggy块在数组的末尾。因此我假设,当我没有0和1时,该块已经“向后移动”了。更令人兴奋的是 - 当我将输入大小增加到5000时,输出完全由0组成。此外,以下代码不起作用:
if(gid == 0)
{
int i, c = 0;
for(i = 0; i < max_id; i++)
{
if(result_mask[i]!=0)
{
c++;
result_mask[i] = 5;
}
else
{
result_mask[i] = 5;
}
}
*result_count = c;
}
if(gid == 3999)
{
int i, c = 0;
for(i = 0; i < max_id; i++)
{
if(result_mask[i]!=0)
{
c++;
result_mask[i] = 5;
}
else
{
result_mask[i] = 5;
}
}
*result_count = c;
}
而只有
if(gid == 3999)
{
int i, c = 0;
for(i = 0; i < max_id; i++)
{
if(result_mask[i]!=0)
{
c++;
result_mask[i] = 5;
}
else
{
result_mask[i] = 5;
}
}
*result_count = c;
}
将起作用(再次,可能使用更大的输入,它可能不起作用)。以下是该设备的一些细节:
Device name: GeForce 9600M GT
Device vendor: NVIDIA
Clock frequency: 1250 MHz
Max compute units: 4
Global memory size: 256 MB
Local memory size:. 16 KB
Max memory allocation size: 128 MB
Max work group size: 512
显然,我错过了这里的大事。我的第一个想法是它存在一些内存冲突,其中80个元素的块被另一个“线程”覆盖。但我想的越多,它的意义就越小。
我会非常感谢任何暗示! 感谢。
编辑: 回复晚了非常抱歉。所以我修改了代码,将其减少到最低限度来重现问题。以下是该计划的c代码:
#include <stdio.h>
#include <stdlib.h>
#include <OpenCL/openCL.h>
#define INPUTSIZE (200)
typedef struct tag_openCL
{
cl_device_id device;
cl_context ctx;
cl_command_queue queue;
cl_program program;
} openCL;
int main(void)
{
int err;
openCL* cl_ctx = malloc(sizeof(openCL));
if(!cl_ctx)
exit(1);
err = clGetDeviceIDs(NULL, CL_DEVICE_TYPE_GPU, 1, &cl_ctx->device, NULL);
cl_ctx->ctx = clCreateContext(0, 1, &cl_ctx->device, clLogMessagesToStdoutAPPLE, NULL, &err);
cl_ctx->queue = clCreateCommandQueue(cl_ctx->ctx, cl_ctx->device, CL_QUEUE_PROFILING_ENABLE, &err);
printf("Successfully created context and queue for openCL device. \n");
/* Build program */
char * kernel_source = "__kernel void \
sel(__global int * input, \
__global int * result_mask, \
const unsigned int max_id) \
{ \
int gid = get_global_id(0); \
\
result_mask[gid] = input[gid] % 2 == 0; \
result_mask[gid] &= (input[gid] + 1) % 3 == 0; \
\
if(gid == 0) { \
int i; \
for(i = 0; i < max_id; i++) { \
if(result_mask[i]) { \
result_mask[i] = 5; \
} \
else { \
result_mask[i] = 5; \
} \
} \
} \
}";
cl_program prog = clCreateProgramWithSource(cl_ctx->ctx, 1, (const char**)&kernel_source, NULL, &err);
cl_ctx->program = prog;
err = clBuildProgram(cl_ctx->program, 0, NULL, NULL, NULL, NULL);
cl_kernel kernel = clCreateKernel(cl_ctx->program, "sel", &err);
/* create dummy input data */
int * input = calloc(sizeof(int), INPUTSIZE);
int k;
for(k = 0; k < INPUTSIZE; k++)
{
input[k] = abs((k % 5) - (k % 3))+ k % 2;
}
cl_mem source, intermediate;
unsigned int problem_size = INPUTSIZE;
source = clCreateBuffer(cl_ctx->ctx, CL_MEM_READ_WRITE, problem_size * sizeof(int), NULL, NULL);
clEnqueueWriteBuffer(cl_ctx->queue, source, CL_TRUE, 0, problem_size * sizeof(int), (void*) input, 0, NULL, NULL);
intermediate = clCreateBuffer(cl_ctx->ctx, CL_MEM_READ_WRITE, problem_size * sizeof(int), NULL, NULL);
int arg = 0;
clSetKernelArg(kernel, arg++, sizeof(cl_mem), &source);
clSetKernelArg(kernel, arg++, sizeof(cl_mem), &intermediate);
clSetKernelArg(kernel, arg++, sizeof(unsigned int), &problem_size);
size_t global_work_size = problem_size;
size_t local_work_size = 1;
clEnqueueNDRangeKernel(cl_ctx->queue, kernel, 1, NULL, &global_work_size, &local_work_size, 0, NULL, NULL);
clFinish(cl_ctx->queue);
// read results
int * result = calloc(sizeof(int), problem_size );
clEnqueueReadBuffer(cl_ctx->queue, intermediate, CL_TRUE, 0, problem_size * sizeof(int), result, 0, NULL, NULL);
clFinish(cl_ctx->queue);
int j;
for(j=1; j<=problem_size; j++)
{
printf("%i \t", result[j-1]);
if(j%10 ==0 && j>0)
printf("\n");
}
return EXIT_SUCCESS;
}
结果仍然是非确定性的,我在输出中的随机位置得到0和1。对于本地工作组大小为1,它们位于数组的前半部分,大小为2 - 在下半部分中,对于大小为4,它看起来对于200个元素是可以的,但是对于一个,它还有0和1的问题规模为400.此外,对于规模为1的全球工作组,一切正常。也就是说,如果我使用两个内核 - 一个用于[问题大小]的全局工作组大小进行并行计算,另一个用全局工作组大小为1,那么一切都很好。同样,我完全清楚这不是这样做的方法(运行这种顺序代码的内核),但是,我想知道,为什么它不起作用,因为它看起来我错过了什么。
谢谢, Vassil
答案 0 :(得分:1)
您的OpenCL代码非常简单,结果非常奇怪。我认为问题可能来自设置部分。缓冲区创建,调用EnqueueNDRange等。你可以发布设置部分吗?我想问题就在那里。
编辑: 看到你的代码并进行测试后,我意识到起初我并没有完全理解你的问题。当你注意到面具更新部分时,我的思绪就是摆脱了那条线。我应该第一次能够正确回答。
问题是您无法同步不同的工作组。 CLK_GLOBAL_MEM_FENCE影响工作组的内存排序访问(确保在回读之前完成对全局内存的写入)。解决问题的真正方法是在两次调用中执行代码,首先并行更新掩码,然后在另一个内核中执行其余内容,这些内容将在第一个完成时执行。在继续之前需要完成整个操作,因此必须在命令队列级别使用障碍。没有其他办法。
从规范中逐字逐句:
OpenCL中有两个同步域:
单个工作组中的工作项
在单个上下文中排队到命令队列的命令