Как ядро взаимодействует с данными в OS X OpenCL
Существует две части каждой программы OpenCL. Часть, работающую на устройстве, вызывают ядром; часть, создающая объекты памяти, затем конфигурирует и вызывает ядро, вызывается узлом и обычно работает на CPU. Ядро является по существу функцией, записанной на языке OpenCL, позволяющем ему быть скомпилированным для выполнения на любом устройстве, поддерживающем OpenCL. Ядро является единственным способом, которым узел может вызвать функцию, которая будет работать на устройстве. Когда узел вызывает ядро, много единиц работы начинают работать на устройстве. Каждая единица работы выполняет код ядра, но работает над другой частью набора данных. Ядро управляет единицами работы путем доступа к ним использующий их IDs с помощью функций такой как get_global_id(…)
и get_local_id(…)
. Несмотря на то, что ядра ставятся в очередь для выполнения хост-приложениями, записанными в C, C++ или Objective C, ядро должно быть скомпилировано отдельно, чтобы быть настроенным для устройства, на котором это собирается работать.
Взаимодействие с ядрами является более простыми инструментами использования, предоставленными OS X, чем это использует стандартный OpenCL. С OS X v10.7, можно включать ядра OpenCL как ресурсы в проектах XCode и скомпилировать их вместе с остальной частью приложения. Также с OS X v10.7, узел может вызвать ядра путем передачи их параметры так же, как если бы они были типичными функциями (см. Передающие Данные К Ядру); больше не необходимо явно установить параметры ядра с помощью специального OpenCL APIs.
Доступ к объектам от ядра
Для устройства для фактической обработки данных необходимо сделать доступные данные к единицам работы, выполняющимся на устройстве.
Передать данные от узла до вычислить ядра:
Подготовьте входные данные.
Укажите, как данные должны быть присвоены единицам работы. Посмотрите Указание, Как Разделить Набор данных.
Создайте буферный и объект (ы) изображения надлежащего размера. Переместите входные данные от использования памяти хоста
gcl_malloc
или различноеgcl_ copy
функции (такой какgcl_memcpy
) к устройству. Посмотрите Объекты Памяти в OS X OpenCL для получения дополнительной информации.Вызовите ядро. В отличие от этого в стандартном OpenCL, Вы не должны явно устанавливать параметры ядра или ставить в очередь ядро очереди команды OpenCL; вместо этого просто поставьте ядро в очередь как блок очереди отгрузки. Посмотрите Передающие Данные К Ядру.
Получите результаты. Посмотрите Результаты Получения Ядра.
Указание, как разделить набор данных
Когда Вы пишете ядро в OpenCL, Вы пишете код, что каждая единица работы будет выполнять-инструкции о том, как обработать одну часть Вашего полного набора данных. Путем запуска многих единиц работы, каждая из которых воздействует на просто небольшую часть данных, Вы заканчиваете тем, что обработали целый набор данных. ndrange структура используется, чтобы указать, как данные присваиваются единицам работы.
N-мерный диапазон (cl_ndrange
) структура, которую Вы передаете ядру, состоит из следующих полей:
size_t work_dim
:Число размерностей для использования для запуска ядра: 1, 2, или 3.
Некоторые проблемы является самым простым разбить на блоки размера ядра при обработке их как одномерных. Пример этого типа проблемы вычисляет хеш md5 для списка 50 миллионов слов. Вы могли записать ядро, вычисляющее хеш md5 для одного слова, и запустите 50 миллионов экземпляров ядра. В этом случае ndrange является диапазоном 1-D: единственный диапазон (0 - 50 миллионов), который имеет только одну координату. Можно думать о нем как как индекс. В Вашем ядре можно вызвать
get_global_id(0)
, и это даст Вам, что координата — значение от 0 до 49,999,999, который представляет индекс в Ваши данные, которые должен обработать этот экземпляр ядра.Если Ваши данные представляют плоское изображение, которое является x пикселями, широкими y пикселями высоко, то у Вас есть двумерный набор данных с каждой точкой данных, представленной ее координатами на осях x и y. Много алгоритмов обработки изображений лучше всего представлены с помощью двумерного ndrange. Скажем, Вы хотите сделать что-то другое к каждому пикселю 2048 x 1 024 изображения. Вы могли записать ядро OpenCL, делающее что-то к единственному пикселю, и затем запускающее его с помощью двумерного ndrange с глобальным размером работы 2048 x 1024. В этом случае Ваше ядро (очевидно), не одномерно. Можно вызвать
get_global_id(0)
иget_global_id(1)
получить x, y координаты этого экземпляра во всем ndrange. Поскольку существует 1 к 1 отображение между ndrange и пикселями, выбирание пикселя для обработки действительно просто; просто вызовите эти две функции.Если Вы имеете дело с пространственными данными, включающими (x, y, z) позиция узлов в трехмерном пространстве, можно использовать трехмерный ndrange.
Другой способ смотреть на размерность Ваших данных с точки зрения вложенных циклов в традиционных, непараллельных приложениях. Если можно циклично выполниться через весь набор данных с единственным циклом, то данные одномерны. При использовании одного цикла, вложенного в другом данные являются двумерными, и если у Вас были бы циклы вложенными глубокий из трех для циклического повторения через все данные, данные являются трехмерными.
global_work_size
:global_work_size
поле указывает размер каждой размерности. Эффективно, это определяет число общих единиц работы, которые будут запущены. Если у Вас есть одномерный диапазон, и Вы хотите обработать миллион вещей, тоglobal_work_size
поле будет{1000000, 0, 0}
. При обработке 2 048 пикселей изображением на 1 024 пикселя Вы установили быwork_dim = 2
иglobal_work_size = { 2048, 1024, 0 }
.Единственное ограничение на
global_work_size
это, размер работы каждой размерности должен быть кратным числомlocal_work_size
из той размерности.global_work_offset
:global_work_offset
поле указывает смещение на размерность для добавления к значениям, возвращеннымget_global_id(…)
. Скажите, например, у Вас есть список одного миллиона слов, и Вы хотите вычислить md5 хеш Word 50,000 - 60,000. Поскольку данные одномерны, ndrange имел бы awork_dim
из1
. Поскольку существует 10 000 элементов, которые будут обработаны, установитьglobal_work_size = {10000, 0, 0}
. Для «пропущения» к Word 50,000 с самого начала установитеglobal_work_offset = {50000, 0, 0}
. Тем путем, самый первый вызов кget_global_id(0)
возвращает 50,000-й пиксель, вторые возвраты 50,001-й пиксель, и т.д.local_work_size
:Рабочая группа является набором единиц работы, выполняющихся на том же, вычисляют модуль на том же устройстве OpenCL. Путем данные разбиты в рабочие группы, может влиять на производительность алгоритма на определенных аппаратных средствах. При постановке в очередь ядра для выполнения на устройстве можно указать размер рабочей группы, которую Вы хотели бы, чтобы OpenCL использовал во время выполнения. Путем обеспечения OpenCL предложенным размером рабочей группы Вы говорите его, как Вы хотели бы, чтобы он делегировал единицы работы к различным вычислительным модулям на устройстве. Единицы работы в рабочей группе имеют уникальную возможность совместно использовать локальную память друг с другом и синхронизироваться друг с другом в указанных программистами барьерах.
local_work_size
дает Вам контроль над размером рабочей группы непосредственно.
Передача данных к ядру
XCode использует Ваш код ядра для автоматической генерации прототипа функции ядра в заголовочном файле ядра. Для передачи данных ядру передайте объекты памяти как параметры (как Вы передали бы параметры любой другой функции) при вызове ядра от кода узла. Параметры ядра OpenCL могут быть ограничены по объему с локальным или глобальным спецификатором, определяя хранение памяти для этих параметров. Это означает, что, с OS X v10.7, параметры ядра объявили с local
или __local
спецификатор адреса объявляется как size_t
в блочном объявлении ядра.
Например, если ядру объявили параметр с local
спецификатор адреса:
kernel void foo( |
global float *a, |
local float *shared); // This kernel parameter is of type |
// local float; will be size_t in the |
// kernel block |
Компилятор генерирует следующее объявление экстерна этого блока ядра:
extern void (^foo_kernel)( |
const cl_ndrange *ndrange, |
float *a, |
size_t shared // In the generated declaration, |
// local float is declared as size_t |
); |
Путем соединения буфера возражает с определенными параметрами ядра, Вы позволяете обработать свои данные с помощью функции ядра. Например, в Примере: Выделение, Используя, и Выпуск Буферных Объектов, замечает, как пример кода обрабатывает входной указатель данных очень, как Вы обработали бы указатель в C. В этом примере входные данные являются массивом float
значения, и можно обработать каждый элемент float
массив путем индексации в указатель.
Получение результатов ядра
Если ядро будет возвращать результаты в буфере, вызовите функцию такой как gcl_memcpy(…)
в то время как в блоке на данной очереди.
Чтобы удостовериться, что результаты все доступны для узла перед продолжением использовать dispatch_sync
или ожидайте с помощью другого метода синхронизации.
Если ядро будет возвращать результаты в буфере, вызовите dispatch_sync
функционируйте как это:
dispatch_sync(queue, |
^{ |
gcl_memcpy(ptr_c, |
device_c, |
num_floats * sizeof(float)); |
}); |
Если ядро будет возвращать результаты в изображении, вызовите dispatch_sync
функционируйте как это:
dispatch_sync(queue,^{ |
size_t origin = {0,0,0}; |
size_t region = {512, 512, 1}; |
gcl_copy_image_to_ptr( |
results_ptr, |
image, |
origin, |
region); |
}); |
Это скопирует байты для 512 x 512 пикселей с изображения на буфер, указанный results_ptr
параметр.