Ядро OpenCL не возвращает символьные данные в хост-программу - PullRequest
1 голос
/ 17 июня 2020

Я новичок в OpenCL и узнал об этом из «OpenCL в действии» Мэтью Скарпино . Я рассмотрел пример умножения матрицы на вектор (стр. 11-13). По какой-то причине пример не работал на моем компьютере. Ядро не вернуло значения. Я начал искать простые примеры вывода данных из ядра.

Я нашел в youtube-канале Уэсли Шиллингфорда пример вывода строки символов «Hello world!» из ядра. На моем домашнем компьютере пример работал. Однако «кухня» OpenCL оставалась закрытой, поскольку представленный пример был написан на C ++. Краткость кода затемняла представление о том, что происходит. Поэтому я начал искать примеры в коде C.

Среди ответов на Stackoverflow я нашел пример минимальной программы OpenCL , которая увеличивает значение в ядре. Я взял этот код за основу для написания своей программы, потому что он прост и удобен для новичка. Как я позже узнал, пример содержал ошибку.

Другой отличный пример убедил меня использовать указатели для возврата данных из ядра. Использование массивов для хранения выходных значений ядра приводит к тому, что значения целевого массива не меняются, а данные из ядра исчезают во время вывода. Я понял, что нам нужно использовать указатели для вывода данных из ядра. Однако мне это не помогло. Проблема передачи данных из ядра в хост-программу остается. Пожалуйста, поправьте меня, если я в чем-то ошибаюсь. Суть топи c: Ядро не возвращает символьные данные в хост-программу. В чем может быть проблема?

#include <CL/cl.h>
#include <stdio.h>  
#include <stdlib.h>

int main(){

    cl_platform_id *platforms =NULL;
    cl_device_id *devices=NULL;
    cl_context context;
    cl_command_queue cmdQueue;
    cl_program program;
    cl_kernel kernel = NULL;
    char *cpOutputData;
    int output_size = 8;
    cl_mem output_buff;
    cl_int status;      // to check the output of each API call

    const char *source =
        "__kernel void Hello( __global char* ch) {\n"
        "   ch[0]='P';"
        "   ch[1]='r';"
        "   ch[2]='i';"
        "   ch[3]='v';"
        "   ch[4]='e';"
        "   ch[5]='t';"
        "   ch[6]='!';"
        "   ch[7]='\0';"
        "}\0";

    printf("GetPlatformIDs... ");
    cl_uint numPlatforms = 0;
    //STEP 1: Discover and initialize platforms
    // Use clGetPlatformIDs to retreive the number of platforms
    status = clGetPlatformIDs(0, 
                              NULL, 
                              &numPlatforms);
    // Allocate enough space for each platform
    platforms = (cl_platform_id*)malloc(numPlatforms*sizeof(cl_platform_id));
    // Fill in platforms with clGetPlatformIDs()
    status=clGetPlatformIDs(numPlatforms, 
                            platforms, 
                            NULL);
    printf("\nNumber of discovered platforms is %d. ", numPlatforms);

    // STEP 2: Discover and initialize devices
    printf("OK.\nGetDeviceIDs... ");
    cl_uint numDevices = 0;
    // Use clGetDeviceIDs() to retrieve the number of devices present
    status = clGetDeviceIDs(platforms[0], 
                            CL_DEVICE_TYPE_CPU, 
                            0, 
                            NULL, 
                            &numDevices);
    // Allocate enough space for each device
    devices = (cl_device_id*)malloc(numDevices*sizeof(cl_device_id));
    // Fill in devices with clGetDeviceIDs()
    clGetDeviceIDs(platforms[0], 
                   CL_DEVICE_TYPE_CPU, 
                   numDevices, 
                   devices, 
                   NULL);
    printf("\nNumber of discovered devices is %d. ", numDevices);

    // STEP 3: Create a context
    printf("OK.\nCreating context... ");
    // Create context using clCreateContext() and associate it with the devices
    context = clCreateContext(NULL, 
                              numDevices, 
                              devices, 
                              NULL, 
                              NULL, 
                              &status);

    // STEP 4: Create a command queue
    printf("OK.\nQueue creating... ");
    cmdQueue = clCreateCommandQueue(context, 
                                    devices[0], 
                                    CL_QUEUE_PROFILING_ENABLE, 
                                    &status);

    // STEP 5: Create device buffers
    printf("OK.\nOutput buffer creating... ");
    output_buff = clCreateBuffer(context, 
                                 CL_MEM_WRITE_ONLY, 
                                 sizeof(char)*output_size, 
                                 NULL, 
                                 &status);

    // STEP 6: Create and compile program
    printf("OK.\nBuilding program... ");
    // Create a program using clCreateProgramWithSource()
    program = clCreateProgramWithSource(context, 
                                        1, 
                                        (const char**)&source, 
                                        NULL, 
                                        &status);
    // Build (compile) the program for the devices with clBuildProgram()
    status=clBuildProgram(program, 
                          numDevices, 
                          devices, 
                          NULL, 
                          NULL, 
                          NULL);

    // STEP 7: Create a kernel
    printf("OK.\nCreating kernel... ");
    kernel = clCreateKernel(program, 
                            "Hello", 
                            &status);

    // STEP 8: Set kernel arguments
    // Associate ouput buffer with the kernel
    printf("OK.\nSetting kernel arguments... ");
    status = clSetKernelArg(kernel, 
                            0, 
                            sizeof(cl_mem), 
                            &output_buff);

    // STEP 9: Configure the work-item structure
    // Define an index space (global work size) of work itmes for execution. 
    // A workgroup size (local work size) is not required, but can be used.
    size_t globalWorkSize[1];
    // There are 'elements' work-items
    globalWorkSize[0] = output_size;

    // STEP 10: Enqueue the kernel for execution
    printf("OK.\nExecuting kernel... ");
    //Execute the kernel by using clEnqueueNDRangeKernel().
    // 'globalWorkSize' is the 1D dimension of the work-items
    clEnqueueNDRangeKernel(cmdQueue, 
                           kernel, 
                           1, 
                           NULL, 
                           globalWorkSize, 
                           NULL, 
                           0, 
                           NULL, 
                           NULL);

    clFinish(cmdQueue);

    // STEP 11: Read the ouput buffer back to the host
    printf("OK.\nReading buffer... ");
    // Allocate space for the data to be read
    cpOutputData = (char*)malloc(output_size*sizeof(char));
    // Use clEnqueueReadBuffer() to read the OpenCL ouput buffer to the host ouput array
    clEnqueueReadBuffer(cmdQueue, 
                        output_buff, 
                        CL_TRUE, 
                        0, 
                        output_size, 
                        cpOutputData, 
                        0, 
                        NULL, 
                        NULL);

    printf("\nPrinting output data: \n");
    printf(cpOutputData);

    // STEP 12: Releasing resources
    printf("\n...Releasing OpenCL resources... ");
    clReleaseKernel(kernel);
    clReleaseProgram(program);
    clReleaseCommandQueue(cmdQueue);
    clReleaseMemObject(output_buff);
    clReleaseContext(context);

    printf("OK.\n...Releasing host resources... ");
    free(cpOutputData);
    free(platforms);
    free(devices);

    printf("OK.\nEnd of program. Bey!\n");
    system("PAUSE");
    return 0;
}

Результат выполнения моей программы здесь .

1 Ответ

1 голос
/ 17 июня 2020

Проблема, с которой вы столкнулись, ОЧЕНЬ незаметна, и, к сожалению, у вас нет проверки ошибок в одном месте, которое могло бы ее обнаружить. В частности, компиляция исходного кода вашего ядра с использованием clBuildProgram не выполняется, и, к сожалению, status не проверяется. Я не уверен, почему остальная часть программы не выдает ошибок в вашей реализации, в моей, конечно же, есть. *

По сути, исходный код вашего ядра для компилятора OpenCL выглядит так:

__kernel void Hello( __global char* ch) {
   ch[0]='P';
   ch[1]='r';
   ch[2]='i';
   ch[3]='v';
   ch[4]='e';
   ch[5]='t';
   ch[6]='!';
   ch[7]='

, потому что escape-код \0 в строковом литерале вставляет в память фактический нулевой символ, который source переменная в конце указывает на, в результате чего она рассматривается как конец исходного кода вашего ядра.

На самом деле вы хотите, чтобы escape-последовательность появлялась в коде вашего ядра OpenCL, поэтому вам нужно ее избежать дважды: один раз для компилятора C вашей основной программы и второй раз для вашего компилятора OpenCL. Это будет:

    "   ch[7]='\\0';"
//              ^--- note second backslash

Двойной backsla sh преобразуется в одиночный backsla sh в вашей строке source, где компилятор OpenCL объединяет его с последующим нулем, чтобы превратить символьный литерал в символ nul.

С этим исправлением все работает!

Я рекомендую писать исходный код ядра в отдельном файле. Либо загрузите этот файл с помощью файлового ввода-вывода в своей программе, либо автоматически создайте литерал для данных, которые будут вставлены в исходный код. Инструмент unix xxd может сделать это с помощью флага -i, вы, вероятно, сможете найти его эквивалент Windows или даже Windows сборку самого инструмента.

...