Поиск…


Вступление

Прежде чем использовать OpenCL, вам нужно настроить свой код, чтобы использовать его. В этом разделе основное внимание уделяется тому, как получить opencl в вашем проекте и запустить базовое ядро. Примеры основаны на оболочке C # OpenCL.NET, но поскольку оболочка не добавляет абстракции в OpenCL, код, вероятно, будет работать с очень небольшими изменениями на C / C ++.

Вызовы на C # могут выглядеть следующим образом: «Cl.GetPlatformIDs». Для C-Style OpenCL Api вы бы назвали 'clGetPlatformIDs' и для стиля C ++ один 'cl :: GetPlatformIDs'

замечания

  • NVidia, AMD и Intel имеют несколько разные реализации OpenCL, но известные отличия (по моему опыту) ограничены требованиями к скобкам и неявными нажатиями. Иногда NVidia будет разбивать ваше ядро, пытаясь выяснить правильную перегрузку метода. В этом случае это помогает предложить явный бросок, чтобы помочь графическому процессору. Проблема наблюдалась для ядер, скомпилированных во время выполнения.

  • Чтобы получить дополнительную информацию об используемых вызовах в этом разделе, достаточно указать google 'OpenCL', за которым следует имя функции. Группа Khronos имеет полную документацию по всем параметрам и типам данных, доступным на их веб-сайте.

Инициализация целевого устройства

Ядро OpenCL можно выполнить либо на графическом процессоре, либо на процессоре. Это позволяет использовать резервные решения, где у клиента может быть очень устаревшая система. Программист также может ограничить свою функциональность либо процессором, либо графическим процессором.

Чтобы начать работу с OpenCL, вам понадобится «Контекст» и «Устройство». Оба являются структурами, определенными OpenCL API (также известными как cl :: Context или clContext & ~ Device) и определяют используемый целевой процессор.

Чтобы получить свое устройство и контекст, вам нужно запросить список доступных платформ, каждый из которых может содержать несколько устройств. Платформа представляет ваш физический GPU и CPU, в то время как устройство может дополнительно различать содержащиеся в нем вычислительные устройства. Для графических процессоров большинство платформ будут иметь только одно устройство. Но процессор может предложить дополнительный встроенный графический процессор рядом с его возможностями процессора.

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

Быстрая заметка API перед началом кодирования: почти каждый вызов OpenCL дает вам значение ошибки, либо как возвращаемое значение, либо через ref-value (указатель на C). Теперь давайте начнем.

ErrorCode err;
var platforms = Cl.GetPlatformIDs(out err);
if(!CheckError(err, "Cl.GetPlatformIDs")) return;
foreach (var platform in platforms) {
    foreach (var device in Cl.GetDeviceIDs(platform, DeviceType.Gpu, out err)) {
        if(!CheckError(err, "Cl.GetDeviceIDs")) continue;
        [...]
    }
}

Этот фрагмент кода запрашивает все доступные устройства GPU в системе. Теперь вы можете добавить их в список или начать свой контекст напрямую с первого совпадения. Функция CheckError (...) - простая утилита, которая проверяет, имеет ли код ошибки значение успеха или другое, и может предложить вам некоторые протоколирования. Рекомендуется использовать отдельную функцию или макрос, потому что вы будете называть это много.

ErrorCode - это просто перечисление на тип данных cl_int для C #, C / C ++ может сравнивать значение int с предопределенными константами ошибок, перечисленными здесь: https://www.khronos.org/registry/OpenCL/sdk/1.0/docs/man/ XHTML / errors.html

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

Cl.GetDeviceInfo(_device, DeviceInfo.ImageSupport, out err)

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

_context = Cl.CreateContext(null, 1, new[] { _device }, ContextNotify, IntPtr.Zero, out err);

Здесь кое-что происходит. Для людей C / C ++ IntPtr является адресом указателя на C #. Я сосредоточусь на важных частях здесь.

  • Второй параметр определяет количество устройств, которые вы хотите использовать.
  • Третий параметр - это массив этих устройств (или указатель на C / C ++)
  • И третий параметр является указателем функции для функции обратного вызова. Эта функция будет использоваться всякий раз, когда ошибки происходят внутри контекста.

Для дальнейшего использования вам необходимо сохранить используемые вами устройства и контекст.

Когда вы закончите все свое взаимодействие OpenCL, вам нужно будет снова освободить контекст

Cl.ReleaseContext(_context);

Компиляция вашего ядра

Ядра могут быть скомпилированы во время выполнения на целевом устройстве. Для этого вам нужно

  • исходный код ядра
  • целевое устройство для компиляции
  • контекст, построенный с целевым устройством

Быстрое обновление терминов: программа содержит коллекцию ядер. Вы можете думать о программе как о полном исходном файле C / C ++ / C #, а ядра - это разные члены функции этого файла.

Сначала вам нужно будет создать программу из исходного кода.

var program = Cl.CreateProgramWithSource(_context, 1, new[] { source }, null, out err);

Вы можете объединить несколько исходных файлов в одну программу и скомпилировать их вместе, что позволит вам иметь ядра в разных файлах и скомпилировать их за один раз.

На следующем шаге вам нужно будет скомпилировать программу на вашем целевом устройстве.

err = Cl.BuildProgram(program, 1, new[] { _device }, string.Empty, null, IntPtr.Zero);

Теперь здесь немного оговорено: код ошибки сообщает вам, был ли сам вызов функции успешным, но не был ли ваш код действительно скомпилирован. Чтобы убедиться в этом, нам нужно запросить дополнительную информацию

BuildStatus status;
status = Cl.GetProgramBuildInfo(program, _device, ProgramBuildInfo.Status, out err).CastTo<BuildStatus>();
if (status != BuildStatus.Success) {
    var log = Cl.GetProgramBuildInfo(program, _device, ProgramBuildInfo.Log, out err);
}

Люди C / C ++ могут игнорировать бросок в конце и просто сравнивать возвращаемое целое с соответствующей константой.

Первый вызов проверяет, действительно ли наша сборка была успешной. Если нет, мы можем отложить журнал и посмотреть, где все пошло не так. См. Замечания для некоторых распространенных ошибок в отношении разных платформ.

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

_kernel = Cl.CreateKernel(_program, kernel, out err);

где «kernel» - это строка имени ядра. Когда вы закончите свое ядро, вам нужно отпустить его с помощью

Cl.ReleaseKernel(_kernel);

Создание очереди команд

Чтобы инициировать любую операцию на ваших устройствах, вам понадобится очередь команд для каждого устройства. Очередь отслеживает различные вызовы, которые вы делали с целевым устройством, и сохраняет их в порядке. Большинство команд также могут выполняться либо в режиме блокировки, либо без блокировки.

Создание очереди довольно просто:

_queue = Cl.CreateCommandQueue(_context, _device, CommandQueueProperties.None, out err);

Основное взаимодействие с вашей командной очередью заключается в том, чтобы вставить различные операции, которые вы хотите выполнить, например, скопировать данные на устройство и с вашего устройства и запустить ядро.

Когда вы закончите использовать очередь команд, вам нужно освободить очередь с вызовом

Cl.ReleaseCommandQueue(_queue);

Выполнение ядра

Итак, теперь мы приходим к реальным вещам, выполняя ваши ядра на параллельном устройстве. Пожалуйста, ознакомьтесь с основными сведениями об оборудовании, чтобы полностью понять диспетчеризацию ядра.

Сначала вам нужно будет задать аргументы ядра, прежде чем вызывать ядро. Это делается через

err = Cl.SetKernelArg(_kernel, $argumentIndex, $argument);

Если вы не зададите каждый аргумент перед запуском ядра, ядро ​​завершится неудачно.

Прежде чем мы начнем наше ядро, нам нужно вычислить «глобальный рабочий размер» и «местный размер работы».

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

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

Теперь, когда мы определили размеры нашей работы, мы можем вызвать ядро.

Event clevent;
err = Cl.EnqueueNDRangeKernel(_queue, _kernel, $dimensions, null, $globalWorkSize, $localWorkSize, 0, null, out clevent);

Размерные $ определяют наше желаемое количество измерений, $ globalWorkSize - это массив размерных размерных размеров с глобальным размером работы и одинаковым для $ localWorkSize. Последний аргумент дает вам объект, который представляет ваше исполняемое в настоящее время ядро.



Modified text is an extract of the original Stack Overflow Documentation
Лицензировано согласно CC BY-SA 3.0
Не связан с Stack Overflow