Основная выборка программирования

Эта глава обеспечивает тур через код простого приложения OpenCL, выполняющего вычисления на наборе данных тестирования. Код в Перечислении 2-2 вызывает ядро, определенное в Перечислении 2-1. Квадраты ядра каждое значение. Как только ядро завершает свою работу, узел проверяет то каждое значение, был обработан ядром.

Пример кода простого ядра

Перечисление 2-1 является кодом ядра в качестве примера. Посмотрите для загрузки проекта. Посмотрите, Как Ядро Взаимодействует С Данными в OS X OpenCL для получения дополнительной информации о передающих параметрах к ядру и информации о получении от ядра.

  Пример кода Ядра перечисления 2-1

// Simple OpenCL kernel that squares an input array.
// This code is stored in a file called mykernel.cl.
// You can name your kernel file as you would name any other
// file.  Use .cl as the file extension for all kernel
// source files.
 
// Kernel block.                                      //   1
kernel void square(                                   //   2
                   global float* input,               //   3
                   global float* output)
{
    size_t i = get_global_id(0);
    output[i] = input[i] * input[i];
}

Примечания:

  1. Оберните свой код ядра в блок ядра:

    kernel void kernelName(
                              global float* inputParameterName,
                              global float* [anotherInputParameter],
                                 …,
                              global float* outputParameterName)
    {
                  ...
    }
  2. Ядра всегда возвращаются void.

  3. Параметры передачи к ядру так же, как Вы передали бы их любой другой функции.

Основной пример кода узла

Перечисление 2-2 является примером кода, который работал бы на узле. Это вызывает ядро к квадратному ряду значений, затем тестирует, чтобы гарантировать, что ядро обработало все данные. Каждая пронумерованная строка в перечислении описана более подробно после перечисления. Посмотрите для загрузки проекта.

  Пример кода Узла перечисления 2-2

#include <stdio.h>
#include <stdlib.h>
 
// This include pulls in everything you need to develop with OpenCL in OS X.
#include <OpenCL/opencl.h>
 
// Include the header file generated by Xcode.  This header file contains the
//  kernel block declaration.                                             // 1
#include "mykernel.cl.h"
 
// Hard-coded number of values to test, for convenience.
#define NUM_VALUES 1024
 
// A utility function that checks that our kernel execution performs the
// requested work over the entire range of data.
static int validate(cl_float* input, cl_float* output) {
    int i;
    for (i = 0; i < NUM_VALUES; i++) {
 
        // The kernel was supposed to square each value.
        if ( output[i] != (input[i] * input[i]) ) {
            fprintf(stdout,
                    "Error: Element %d did not match expected output.\n", i);
            fprintf(stdout,
                    "       Saw %1.4f, expected %1.4f\n", output[i],
                            input[i] * input[i]);
            fflush(stdout);
            return 0;
        }
    }
    return 1;
}
 
int main (int argc, const char * argv[]) {
    int i;
    char name[128];
 
    // First, try to obtain a dispatch queue that can send work to the
    // GPU in our system.                                             // 2
    dispatch_queue_t queue =
               gcl_create_dispatch_queue(CL_DEVICE_TYPE_GPU, NULL);
 
    // In the event that our system does NOT have an OpenCL-compatible GPU,
    // we can use the OpenCL CPU compute device instead.
    if (queue == NULL) {
        queue = gcl_create_dispatch_queue(CL_DEVICE_TYPE_CPU, NULL);
    }
 
    // This is not required, but let's print out the name of the device
    // we are using to do work.  We could use the same function,
    // clGetDeviceInfo, to obtain all manner of information about the device.
    cl_device_id gpu = gcl_get_device_id_with_dispatch_queue(queue);
    clGetDeviceInfo(gpu, CL_DEVICE_NAME, 128, name, NULL);
    fprintf(stdout, "Created a dispatch queue using the %s\n", name);
 
    // Here we hardcode some test data.
    // Normally, when this application is running for real, data would come from
    // some REAL source, such as a camera, a sensor, or some compiled collection
    // of statistics—it just depends on the problem you want to solve.
    float* test_in = (float*)malloc(sizeof(cl_float) * NUM_VALUES);
    for (i = 0; i < NUM_VALUES; i++) {
        test_in[i] = (cl_float)i;
    }
 
    // Once the computation using CL is done, will have to read the results
    // back into our application's memory space.  Allocate some space for that.
    float* test_out = (float*)malloc(sizeof(cl_float) * NUM_VALUES);
 
    // The test kernel takes two parameters: an input float array and an
    // output float array.  We can't send the application's buffers above, since
    // our CL device operates on its own memory space.  Therefore, we allocate
    // OpenCL memory for doing the work.  Notice that for the input array,
    // we specify CL_MEM_COPY_HOST_PTR and provide the fake input data we
    // created above.  This tells OpenCL to copy the data into its memory
    // space before it executes the kernel.                               // 3
    void* mem_in  = gcl_malloc(sizeof(cl_float) * NUM_VALUES, test_in,
                               CL_MEM_READ_ONLY | CL_MEM_COPY_HOST_PTR);
 
    // The output array is not initalized; we're going to fill it up when
    // we execute our kernel.                                             // 4
    void* mem_out =
           gcl_malloc(sizeof(cl_float) * NUM_VALUES, NULL, CL_MEM_WRITE_ONLY);
 
    // Dispatch the kernel block using one of the dispatch_ commands and the
    // queue created earlier.                                            // 5
 
    dispatch_sync(queue, ^{
        // Although we could pass NULL as the workgroup size, which would tell
        // OpenCL to pick the one it thinks is best, we can also ask
        // OpenCL for the suggested size, and pass it ourselves.
        size_t wgs;
        gcl_get_kernel_block_workgroup_info(square_kernel,
                                            CL_KERNEL_WORK_GROUP_SIZE,
                                            sizeof(wgs), &wgs, NULL);
 
        // The N-Dimensional Range over which we'd like to execute our
        // kernel.  In this case, we're operating on a 1D buffer, so
        // it makes sense that the range is 1D.
        cl_ndrange range = {                                              // 6
            1,                     // The number of dimensions to use.
 
            {0, 0, 0},             // The offset in each dimension.  To specify
                                   // that all the data is processed, this is 0
                                   // in the test case.                   // 7
 
            {NUM_VALUES, 0, 0},    // The global range—this is how many items
                                   // IN TOTAL in each dimension you want to
                                   // process.
 
            {wgs, 0, 0}            // The local size of each workgroup.  This
                                   // determines the number of work items per
                                   // workgroup.  It indirectly affects the
                                   // number of workgroups, since the global
                                   // size / local size yields the number of
                                   // workgroups.  In this test case, there are
                                   // NUM_VALUE / wgs workgroups.
        };
        // Calling the kernel is easy; simply call it like a function,
        // passing the ndrange as the first parameter, followed by the expected
        // kernel parameters.  Note that we case the 'void*' here to the
        // expected OpenCL types.  Remember, a 'float' in the
        // kernel, is a 'cl_float' from the application's perspective.   // 8
 
        square_kernel(&range,(cl_float*)mem_in, (cl_float*)mem_out);
 
        // Getting data out of the device's memory space is also easy;
        // use gcl_memcpy.  In this case, gcl_memcpy takes the output
        // computed by the kernel and copies it over to the
        // application's memory space.                                   // 9
 
        gcl_memcpy(test_out, mem_out, sizeof(cl_float) * NUM_VALUES);
 
    });
 
 
    // Check to see if the kernel did what it was supposed to:
    if ( validate(test_in, test_out)) {
        fprintf(stdout, "All values were properly squared.\n");
    }
 
    // Don't forget to free up the CL device's memory when you're done. // 10
    gcl_free(mem_in);
    gcl_free(mem_out);
 
    // And the same goes for system memory, as usual.
    free(test_in);
    free(test_out);
 
    // Finally, release your queue just as you would any GCD queue.    // 11
    dispatch_release(queue);
}
 

Примечания:

  1. Включайте заголовочный файл, содержащий блочное объявление ядра. Имя заголовочного файла для .cl файла будет именем .cl файла с.h, добавленным к нему. Например, если называют .cl файл mykernel.cl, заголовочный файл, который необходимо включать, будет mykernel.cl.h.

  2. Вызвать gcl_create_dispatch_queue создать очередь отгрузки.

  3. Создайте объекты памяти содержать данные ввода и вывода и входные данные записи к входным объектам. Выделите массив на устройстве OpenCL, из которого можно считать результаты ядра назад в память хоста. Использовать gcl_malloc и удостоверьтесь, что использовали размер OpenCL возвращаемого типа данных. Например, записать gcl_malloc(sizeof(cl_float) * NUM_VALUES. Поскольку устройство CL воздействует на его собственное пространство памяти, выделите память OpenCL для входных данных, на которые будет работать ядро. Указать CL_MEM_COPY_HOST_PTR сказать OpenCL копировать по входным данным с памяти хоста в его пространство памяти, прежде чем это выполнит ядро.

  4. Выделите память OpenCL, в которой ядро сохранит свои результаты.

  5. Диспетчеризируйте свой блок ядра с помощью одной из команд отгрузки и очереди, которую Вы создали выше. В Вашем вызове отгрузки можно указать параметры рабочей группы.

  6. Опишите диапазон параллели данных (ndrange), по которому можно выполнить ядро в cl_ndrange структура.

    OpenCL всегда выполняет ядра способом параллели данных — т.е. экземпляры того же ядра (единицы работы) выполняются на различных частях общего набора данных. Каждая единица работы ответственна за выполнение ядра один раз и работа на ее присвоенной части набора данных.

    Вы используете cl_ndrange поле, чтобы указать, как должны быть организованы рабочие группы. Для получения дополнительной информации посмотрите Указание, Как Разделить Набор данных.

  7. Всегда передавайте смещение для каждого из трех измерений даже при том, что рабочая группа может иметь меньше, чем три измерения. Посмотрите Указание, Как Разделить Набор данных для получения дополнительной информации.

  8. Вызовите ядро, поскольку Вы вызвали бы функцию. Передайте ndrange как первый параметр, сопровождаемый ожидаемыми параметрами ядра. Случитесь void* типы к ожидаемым типам OpenCL. Помните, если Вы используете float в Вашем ядре это - a cl_float с точки зрения приложения. Вызов к ядру будет выглядеть примерно так:

    kernelName(
           &ndrange,
           (cl_datatype*)inputArray,
           (cl_datatype*)outputArray);
  9. Получите данные от пространства памяти устройства OpenCL с gcl_memcpy. Вывод, вычисленный ядром, копируется в пространство памяти хост-приложения.

  10. Свободные объекты памяти OpenCL.

  11. Вызвать dispatch_release(...) на очереди отгрузки Вы создали с gcl_create_dispatch_queue(...), как только Вы сделаны с ним.