Проблема с перекомпилированием ядра OpenCL, замедляющая работу программы и возможные проблемы с памятью из-за этого

Я новичок в OpenCL и использую OS X 10.6 с графической картой Nvidia 330. Я работаю над симуляцией ткани на С++, для которой мне удалось написать ядро, которое компилируется и запускается. Проблема в том, что он работает медленнее, чем на процессоре без OpenCL. Я считаю, что причина этого в том, что каждый раз, когда я вызываю метод update() для выполнения некоторых вычислений, я устанавливаю контекст и устройство, а затем перекомпилирую ядро ​​​​из исходного кода.

Чтобы решить эту проблему, я попытался инкапсулировать различные типы OpenCL, которые мне нужны, в класс имитации ткани, чтобы попытаться сохранить их там, а затем создал initCL() для установки этих значений. Затем я создал runCL() для запуска ядра. Как ни странно, это только дает мне проблему с памятью, когда я разделяю материал OpenCL на два метода. Он отлично работает, если initCL() и runCL() объединены в один метод, поэтому я немного застрял.

Программа компилируется и запускается, но затем я получаю SIGABRT или EXC BAD ACCESS в точке, отмеченной в коде runCL(). Когда я получаю SIGABRT, я получаю сообщение об ошибке CL_INVALID_COMMAND_QUEUE, но я не могу понять, почему это происходит только тогда, когда я разделяю два метода. Иногда я получаю SIGABRT, когда утверждение терпит неудачу, чего и следовало ожидать, но в других случаях я просто получаю ошибку неправильного доступа к памяти при попытке записи в буфер.

Кроме того, если кто-нибудь может сказать мне лучший способ/право сделать это или если перекомпиляция JIT не является тем, что замедляет мой код, я был бы очень благодарен, потому что я смотрел на это слишком долго!

Спасибо,

Джон

Инициализация переменных OpenCL Код:

int VPESimulationCloth::initCL(){
   // Find the CPU CL device, as a fallback
   err = clGetDeviceIDs(NULL, CL_DEVICE_TYPE_CPU, 1, &device, NULL);
   assert(err == CL_SUCCESS);

   // Find the GPU CL device, this is what we really want
// If there is no GPU device is CL capable, fall back to CPU
  err = clGetDeviceIDs(NULL, CL_DEVICE_TYPE_GPU, 1, &device, NULL);
if (err != CL_SUCCESS) err = clGetDeviceIDs(NULL, CL_DEVICE_TYPE_CPU, 1, &device, NULL);
assert(device);

// Get some information about the returned device
cl_char vendor_name[1024] = {0};
cl_char device_name[1024] = {0};
err = clGetDeviceInfo(device, CL_DEVICE_VENDOR, sizeof(vendor_name), 
                vendor_name, &returned_size);
err |= clGetDeviceInfo(device, CL_DEVICE_NAME, sizeof(device_name), 
                 device_name, &returned_size);
assert(err == CL_SUCCESS);
//printf("Connecting to %s %s...\n", vendor_name, device_name);

// Now create a context to perform our calculation with the 
// specified device 
context = clCreateContext(0, 1, &device, NULL, NULL, &err);
assert(err == CL_SUCCESS);

// And also a command queue for the context
cmd_queue = clCreateCommandQueue(context, device, 0, NULL);

// Load the program source from disk
// The kernel/program should be in the resource directory
const char * filename = "clothSimKernel.cl";
char *program_source = load_program_source(filename);


program[0] = clCreateProgramWithSource(context, 1, (const char**)&program_source,
                             NULL, &err);
if (!program[0])
{
   printf("Error: Failed to create compute program!\n");
   return EXIT_FAILURE;
}
assert(err == CL_SUCCESS);

err = clBuildProgram(program[0], 0, NULL, NULL, NULL, NULL);
if (err != CL_SUCCESS)
{
   char build[2048];
   clGetProgramBuildInfo(program[0], device, CL_PROGRAM_BUILD_LOG, 2048, build, NULL);
   printf("Build Log:\n%s\n",build);
   if (err == CL_BUILD_PROGRAM_FAILURE) {
      printf("CL_BUILD_PROGRAM_FAILURE\n");
   }
}
if (err != CL_SUCCESS) {
   cout<<getErrorDesc(err)<<endl;
}
assert(err == CL_SUCCESS);
//writeBinaries();
// Now create the kernel "objects" that we want to use in the example file 
kernel[0] = clCreateKernel(program[0], "clothSimulation", &err);

}

Метод выполнения кода ядра:

int VPESimulationCloth::runCL(){

// Find the GPU CL device, this is what we really want
// If there is no GPU device is CL capable, fall back to CPU
err = clGetDeviceIDs(NULL, CL_DEVICE_TYPE_GPU, 1, &device, NULL);
if (err != CL_SUCCESS) err = clGetDeviceIDs(NULL, CL_DEVICE_TYPE_CPU, 1, &device, NULL);
assert(device);

// Get some information about the returned device
cl_char vendor_name[1024] = {0};
cl_char device_name[1024] = {0};
err = clGetDeviceInfo(device, CL_DEVICE_VENDOR, sizeof(vendor_name), 
                vendor_name, &returned_size);
err |= clGetDeviceInfo(device, CL_DEVICE_NAME, sizeof(device_name), 
                 device_name, &returned_size);
assert(err == CL_SUCCESS);
//printf("Connecting to %s %s...\n", vendor_name, device_name);

// Now create a context to perform our calculation with the 
// specified device 

//cmd_queue = clCreateCommandQueue(context, device, 0, NULL);
//memory allocation
cl_mem nowPos_mem, prevPos_mem, rForce_mem, mass_mem, passive_mem,    canMove_mem,numPart_mem, theForces_mem, numForces_mem, drag_mem, answerPos_mem;

// Allocate memory on the device to hold our data and store the results into
buffer_size = sizeof(float4) * numParts;

// Input arrays 
//------------------------------------
// This is where the error occurs
nowPos_mem = clCreateBuffer(context, CL_MEM_READ_ONLY, buffer_size, NULL, NULL);
err = clEnqueueWriteBuffer(cmd_queue, nowPos_mem, CL_TRUE, 0, buffer_size,
                    (void*)nowPos, 0, NULL, NULL);
if (err != CL_SUCCESS) {
  cout<<getErrorDesc(err)<<endl;
}
assert(err == CL_SUCCESS);
//------------------------------------
prevPos_mem = clCreateBuffer(context, CL_MEM_READ_ONLY, buffer_size, NULL, NULL);
err = clEnqueueWriteBuffer(cmd_queue, prevPos_mem, CL_TRUE, 0, buffer_size,
                    (void*)prevPos, 0, NULL, NULL);
assert(err == CL_SUCCESS);
rForce_mem = clCreateBuffer(context, CL_MEM_READ_ONLY, buffer_size, NULL, NULL);
err = clEnqueueWriteBuffer(cmd_queue, rForce_mem, CL_TRUE, 0, buffer_size,
                    (void*)rForce, 0, NULL, NULL);
assert(err == CL_SUCCESS);
mass_mem = clCreateBuffer(context, CL_MEM_READ_ONLY, buffer_size, NULL, NULL);
err = clEnqueueWriteBuffer(cmd_queue, mass_mem, CL_TRUE, 0, buffer_size,
                    (void*)mass, 0, NULL, NULL);
assert(err == CL_SUCCESS);
answerPos_mem = clCreateBuffer(context, CL_MEM_READ_WRITE, buffer_size, NULL, NULL);
//uint buffer
buffer_size = sizeof(uint) * numParts;
passive_mem = clCreateBuffer(context, CL_MEM_READ_ONLY, buffer_size, NULL, NULL);
err = clEnqueueWriteBuffer(cmd_queue, passive_mem, CL_TRUE, 0, buffer_size,
                    (void*)passive, 0, NULL, NULL);
assert(err == CL_SUCCESS);
canMove_mem = clCreateBuffer(context, CL_MEM_READ_ONLY, buffer_size, NULL, NULL);
err = clEnqueueWriteBuffer(cmd_queue, canMove_mem, CL_TRUE, 0, buffer_size,
                    (void*)canMove, 0, NULL, NULL);
assert(err == CL_SUCCESS);

buffer_size = sizeof(float4) * numForces;
theForces_mem = clCreateBuffer(context, CL_MEM_READ_ONLY, buffer_size, NULL, NULL);
err = clEnqueueWriteBuffer(cmd_queue, theForces_mem, CL_TRUE, 0, buffer_size,
                    (void*)theForces, 0, NULL, NULL);
assert(err == CL_SUCCESS);

//drag float
buffer_size = sizeof(float);
drag_mem = clCreateBuffer(context, CL_MEM_READ_ONLY, buffer_size, NULL, NULL);
err |= clEnqueueWriteBuffer(cmd_queue, drag_mem, CL_TRUE, 0, buffer_size,
                    (void*)drag, 0, NULL, NULL);
assert(err == CL_SUCCESS);

// Now setup the arguments to our kernel
err  = clSetKernelArg(kernel[0],  0, sizeof(cl_mem), &nowPos_mem);
err |= clSetKernelArg(kernel[0],  1, sizeof(cl_mem), &prevPos_mem);
err |= clSetKernelArg(kernel[0],  2, sizeof(cl_mem), &rForce_mem);
err |= clSetKernelArg(kernel[0],  3, sizeof(cl_mem), &mass_mem);
err |= clSetKernelArg(kernel[0],  4, sizeof(cl_mem), &passive_mem);
err |= clSetKernelArg(kernel[0],  5, sizeof(cl_mem), &canMove_mem);
err |= clSetKernelArg(kernel[0],  6, sizeof(cl_mem), &numParts);
err |= clSetKernelArg(kernel[0],  7, sizeof(cl_mem), &theForces_mem);
err |= clSetKernelArg(kernel[0],  8, sizeof(cl_mem), &numForces);
err |= clSetKernelArg(kernel[0],  9, sizeof(cl_mem), &drag_mem);
err |= clSetKernelArg(kernel[0],  10, sizeof(cl_mem), &answerPos_mem);
if (err != CL_SUCCESS) {
   cout<<getErrorDesc(err)<<endl;
}
assert(err == CL_SUCCESS);
// Run the calculation by enqueuing it and forcing the 
// command queue to complete the task
size_t global_work_size = numParts;
size_t local_work_size = global_work_size/8;
err = clEnqueueNDRangeKernel(cmd_queue, kernel[0], 1, NULL, 
                     &global_work_size, &local_work_size, 0, NULL, NULL);
if (err != CL_SUCCESS) {
   cout<<getErrorDesc(err)<<endl;
}

assert(err == CL_SUCCESS);
//clFinish(cmd_queue);

// Once finished read back the results from the answer 
// array into the results array
//reset the buffer first
buffer_size = sizeof(float4) * numParts;
err = clEnqueueReadBuffer(cmd_queue, answerPos_mem, CL_TRUE, 0, buffer_size, 
                   answerPos, 0, NULL, NULL);
if (err != CL_SUCCESS) {
   cout<<getErrorDesc(err)<<endl;
}


//cl mem
clReleaseMemObject(nowPos_mem);
clReleaseMemObject(prevPos_mem);
clReleaseMemObject(rForce_mem);
clReleaseMemObject(mass_mem);
clReleaseMemObject(passive_mem);
clReleaseMemObject(canMove_mem);
clReleaseMemObject(theForces_mem);
clReleaseMemObject(drag_mem);
clReleaseMemObject(answerPos_mem);
clReleaseCommandQueue(cmd_queue);
clReleaseContext(context);
assert(err == CL_SUCCESS);
return err;

}

person Jon Hocking    schedule 01.12.2010    source источник
comment
Вопрос также опубликован здесь khronos.org/message_boards/viewtopic.php?f= 37&t=3296   -  person Jon Hocking    schedule 01.12.2010


Ответы (2)


Проблема решена! В нижней части метода runCL() я «освобождал» все свои типы cl, хотя я просто освобождал некоторые cl_mem, но при ближайшем рассмотрении я освобождал контекст и т. д. Как всегда очевидная и досадная ошибка :).

Спасибо andrew.brownsword на форумах Khronos за то, что заметил это.

person Jon Hocking    schedule 01.12.2010
comment
Спасибо, что нашли время вернуться и рассказать нам, как вы решили проблему сбоя. - person Tom; 02.12.2010

Молодец, что решил основную проблему.

Что касается производительности, numParts это большое число? Общий размер работы должен быть большим, чтобы обеспечить насыщение устройства работой, например. десятки тысяч. В идеале локальный рабочий размер (при линеаризации) должен быть кратен 32, наилучшее значение будет зависеть от вашего ядра.

Обычно для локального рабочего размера устанавливается некоторая константа или некоторое значение, зависящее от вашего ядра (вы можете запросить информацию, например, максимальный локальный рабочий размер), поскольку numParts/8 может привести к сбоям при запуске, если он станет слишком большим (предел зависит от конкретного ядра). и конкретное устройство).

person Tom    schedule 02.12.2010
comment
Спасибо что подметил это. Я тестировал это на довольно маленьком кусочке ткани размером 32x32, так как раньше запускал его на процессоре. Я обнаружил эту проблему с размером работы после того, как исправил основную проблему и переместил все на графический процессор, чтобы увеличить размер ткани. Я попробую использовать константу, как вы предложили, и сделаю несколько запросов, чтобы найти наилучшее значение. - person Jon Hocking; 03.12.2010