Используя центральную отгрузку с OpenCL
С OS X v10.7, разработчики OpenCL могут ставить в очередь работу, кодированную, поскольку ядра OpenCL очередям Grand Central Dispatch (GCD), поддержанным OpenCL, вычисляют устройства. Можно использовать GCD с OpenCL к:
-
Исследуйте вычислительную среду, в которой работает Ваше приложение OpenCL. В частности можно учиться, какие устройства в системе были бы лучшими для выполнения определенных вычислений OpenCL и операций:
-
Можно узнать о вычислительном питании и технических характеристиках каждого OpenCL-способного устройства в системе. Посмотрите, что Доступное Обнаружение Вычисляет Устройства.
-
GCD может предложить, какое устройство (а) OpenCL было бы лучшим для выполнения определенного ядра.
-
Можно получить рекомендации о том, как сконфигурировать ядра. Например, можно получить предложенный оптимальный размер рабочей группы для каждого ядра на любом определенном устройстве. Посмотрите Примечания.
-
-
Ставьте в очередь ядро.
-
Синхронизируйте работу между узлом и устройствами OpenCL и синхронизируйте работу между устройствами.
Ваш узел может ожидать на завершении работы во всех очередях (см. Используя GCD Для Синхронизации Узла С OpenCL), или одна очередь может ожидать на завершении другой очереди (см. Синхронизирующиеся Многократные Очереди).
Доступное обнаружение вычисляет устройства
Ядра OpenCL принимают Единственную Инструкцию, Многократные Данные (SIMD) параллельная модель вычисления. В SIMD Вам разделили большой объем данных на блоки, и Вы хотите, чтобы ядро выполнило то же вычисление на каждом блоке.
Некоторые алгоритмы SIMD выполняют лучше на CPUs; другие выполняют лучше на GPUs; некоторая работа лучше над определенными видами GPUs, а не над другими. Инструменты в OS X v10.7 и позже упрощают открытие типов устройств, которые доступны для обработки данных.
Для приобретения знаний о среде, в которой ядра OpenCL будут работать, необходимо получить глобальный контекст по умолчанию. Контекст дает Вам информацию о наборе устройств, память, доступная для тех устройств, и одна или более очередей раньше планировали выполнение одного или более ядер.
От контекста Ваше приложение может обнаружить типы устройств в системе и может получить рекомендации относительно оптимальной конфигурации для выполнения ядра. Как только это знает контекст, Ваше приложение может обратиться к GCD с просьбой создавать очередь для определенного типа устройства или создавать очередь для определенного устройства.
Для обнаружения о доступном вычисляют устройства, приложение:
Вызовы
gcl_get_context
функция для получения «глобального» контекста OpenCL, который OS X создает для Вас.Вызовы
clGetDeviceIds
(...) функция (API в стандарте OpenCL API), указывая контекст Вы просто получили как параметр контекста. Этот вызов возвращает список IDs присоединенных устройств OpenCL. Посмотрите Спецификацию OpenCL для подробных данных об этой функции.Можно принять решение отправить различные типы работы к устройству в зависимости от его характеристик и возможностей. Как только у Вас есть IDs устройств в контексте, вызовите
clGetDeviceInfo
(), функционируют для каждого из устройств для получения информации об устройстве. Пример кода в Перечислении 6-1 запрашивает поставщика (производитель) и имя устройства. Вы могли также использоватьclGetDeviceInfo
(), функция для запроса большей технической информации, такой как число вычисляют ядра, размер строки кэша и т.д. Посмотрите Спецификацию OpenCL для получения информации о типах информации, которую можно получить.
Постановка в очередь ядро очереди отгрузки
Ваше приложение должно использовать OpenCL-совместимую очередь отгрузки для своей работы OpenCL. Можно создать очередь для определенного устройства в системе, или можно создать очередь для определенного типа устройства. Можно ставить в очередь столько ядер на каждой очереди, сколько Вы выбираете. Можно создать столько различных очередей, сколько Вы хотели бы:
-
Для создания очереди отгрузки для работы любого устройства, пока это имеет определенный тип вызовите
gcl_create_dispatch_queue
функциональная передачаCL_DEVICE_TYPE_CPU
,CL_DEVICE_TYPE_GPU
, илиCL_DEVICE_TYPE_ACCELERATOR
как первый параметр.OpenCL OS X создаст очередь отгрузки, использующую GPU или CPU, в зависимости от типа устройства, который Вы указали. Если больше чем один GPU доступен, OS X, OpenCL ставит в очередь ядро на устройстве типа, который Вы указываете, это имеет наибольшее число, вычисляют ядра.
-
Если Вы знаете точно, какое устройство OpenCL ID Вы хотите использовать, потому что Вы получили его с
clGetDeviceIds
функционируйте и узнанный об этом с помощьюclGetDeviceInfo
функция, вызовитеcl_create_dispatch_queue
функция сCL_DEVICE_TYPE_USE_ID
и передайте ID устройства, которое Вы хотите использовать.
Оба из этих методов enqueing ядро на очереди отгрузки проиллюстрированы в Перечислении 6-1.
Как только Вы создали очередь, можно ставить в очередь столько же ядер на ту очередь по мере необходимости. Или, можно создать дополнительные очереди с различными характеристиками.
Для получения дополнительной информации об очередях GCD, см. Руководство по программированию Параллелизма: Очереди Отгрузки.
Определение характеристик ядра на устройстве
Получить информацию, определенную для пары ядра/устройства, такой как, сколько частная и локальная память ядро использует на устройстве или оптимальном размере рабочей группы для выполнения, вызов gcl_get_kernel_block_workgroup_info
функция. Эта информация полезна при настройке производительности для ядра, работающего на определенном устройстве или отлаживающего проблемы производительности.
Можно использовать предложенный размер рабочей группы, возвращенный gcl_get_kernel_block_workgroup_info
функция для определенного ядра на определенном устройстве как cl_ndrange.local_work_size
.
Пример кода: создание очереди отгрузки
Перечисление 6-1 демонстрирует, как получить глобальный контекст OpenCL, и как спросить тот контекст об устройствах, которые это содержит. Это также показывает, как создать очередь отгрузки путем выяснения тип устройства (CPU или GPU), и путем указания устройства OpenCL очереди непосредственно.
Перечисление 6-2 показывает, как получить информацию рабочей группы - полезный для получения пиковой производительности - от блока ядра.
Перечисление 6-1 , Создающее очередь отгрузки
#include <stdio.h> |
// Include OpenCL/opencl.h to include everything you need for OpenCL |
// development on OS X v10.7 or later. |
#include <OpenCL/opencl.h> |
// In this example, mykernel.cl.h is the header file that contains |
// the kernel block declaration. The name of this header file would |
// be different if the name of the file containing the kernel source |
// were different. |
// This header file is generated by Xcode. |
#include "mykernel.cl.h" |
static void print_device_info(cl_device_id device) { |
char name[128]; |
char vendor[128]; |
clGetDeviceInfo(device, CL_DEVICE_NAME, 128, name, NULL); |
clGetDeviceInfo(device, CL_DEVICE_VENDOR, 128, vendor, NULL); |
fprintf(stdout, "%s : %s\n", vendor, name); |
} |
// Demonstrates how to get the global OpenCL context, and how to ask that |
// context about the devices it contains. It also shows how |
// to create a dispatch queue by asking for a device type (CPU or GPU) and |
// by specifying the queue's OpenCL device directly. |
static void hello_world_sample1 () |
{ |
int i; |
// Ask for the global OpenCL context: |
// Note: If you will not be enqueing to a specific device, you do not need |
// to retrieve the context. |
cl_context context = gcl_get_context(); |
// Query this context to see what kinds of devices are available. |
size_t length; |
cl_device_id devices[8]; |
clGetContextInfo( |
context, CL_CONTEXT_DEVICES, sizeof(devices), devices, &length); |
// Walk over these devices, printing out some basic information. You could |
// query any of the information available about the device here. |
fprintf(stdout, "The following devices are available for use:\n"); |
int num_devices = (int)(length / sizeof(cl_device_id)); |
for (i = 0; i < num_devices; i++) { |
print_device_info(devices[i]); |
} |
// To do any work, you need to create a dispatch queue associated |
// with some OpenCL device. You can either let the system give you |
// a GPU—perhaps the only GPU—or the CPU device. Or, you can |
// create a dispatch queue with a cl_device_id you specify. This |
// device id comes from the OpenCL context, as above. Below are three |
// examples. |
// 1. Ask for a GPU-based dispatch queue; notice that here we do not provide |
// a device id. Instead, we let the system tell us the most capable GPU. |
dispatch_queue_t gpu_queue = |
gcl_create_dispatch_queue(CL_DEVICE_TYPE_GPU, NULL); |
// Get the device from the queue, so we can ask OpenCL questions about it. |
// Note that we check to make sure there WAS an OpenCL-capable GPU in the |
// system by checking against a NULL return value. |
if (gpu_queue != NULL) { |
cl_device_id gpu_device = |
gcl_get_device_id_with_dispatch_queue(gpu_queue); |
fprintf(stdout, "\nAsking for CL_DEVICE_TYPE_GPU gives us:\n"); |
print_device_info(gpu_device); |
} else { |
fprintf(stdout, "\nYour system does not contain an OpenCL-compatible " |
"GPU\n."); |
} |
// 2. Try the same thing for CL_DEVICE_TYPE_CPU. All Mac |
// systems have a CPU OpenCL device, so you don't have to |
// check for NULL, as you have to do in the case of a GPU. |
dispatch_queue_t cpu_queue = |
gcl_create_dispatch_queue(CL_DEVICE_TYPE_CPU, NULL); |
cl_device_id cpu_device = gcl_get_device_id_with_dispatch_queue(cpu_queue); |
fprintf(stdout, "\nAsking for CL_DEVICE_TYPE_CPU gives us:\n"); |
print_device_info(cpu_device); |
// 3. Or perhaps you are in a situation where you want a specific device |
// from the list of devices you found on the context. |
// Notice the difference here: |
// Pass CL_DEVICE_TYPE_USE_ID and a device_id. This example just uses the |
// first device on the context from above, whatever that might be. |
dispatch_queue_t custom_queue = |
gcl_create_dispatch_queue(CL_DEVICE_TYPE_USE_ID, devices[0]); |
cl_device_id custom_device = |
gcl_get_device_id_with_dispatch_queue(custom_queue); |
fprintf(stdout, |
"\nAsking for CL_DEVICE_TYPE_USE_ID and our own device gives us:\n"); |
print_device_info(custom_device); |
// Now you can use any of these 3 dispatch queues to run some kernels. |
… // Run your kernels here. |
// Use the GCD API to free your queues. |
dispatch_release(custom_queue); |
dispatch_release(cpu_queue); |
if (gpu_queue != NULL) dispatch_release(gpu_queue); |
} |
Перечисление 6-2 Получая информацию рабочей группы
// This listing shows how to obtain workgroup info – |
// useful for obtaining peak performance—from the kernel block. |
static void hello_world_sample2() { |
// Get a queue backed by a GPU for running our squaring kernel. |
dispatch_queue_t queue = |
gcl_create_dispatch_queue(CL_DEVICE_TYPE_GPU, NULL); |
// Did we get a GPU? If not, fall back to the CPU device. |
if (queue == NULL) { |
gcl_create_dispatch_queue(CL_DEVICE_TYPE_GPU, NULL); |
} |
// In any case, print out the device you're using: |
fprintf(stdout, "\nExamining workgroup info for square_kernel on device "); |
print_device_info(gcl_get_device_id_with_dispatch_queue(queue)); |
// Now find out what OpenCL thinks is the best workgroup size for |
// executing this kernel on this particular device. Notice that this |
// method is executed in a block, on a dispatch queue you've created |
// with OpenCL. |
dispatch_sync(queue, |
^{ |
size_t wgs, preferred_wgs_multiple; |
cl_ulong local_memsize, private_memsize; |
// The next two calls give you information about how much |
// memory, local and private, is used by the kernel on this |
// particular device. |
gcl_get_kernel_block_workgroup_info(square_kernel, |
CL_KERNEL_LOCAL_MEM_SIZE, |
sizeof(local_memsize), |
&local_memsize, NULL); |
fprintf(stdout, "Local memory size: %lld\n", local_memsize); |
gcl_get_kernel_block_workgroup_info(square_kernel, |
CL_KERNEL_PRIVATE_MEM_SIZE, |
sizeof(private_memsize), |
&private_memsize, NULL); |
fprintf(stdout, |
"Private memory size: %lld\n", private_memsize); |
// Ask OpenCL to suggest the optimal workgroup |
// size for this kernel on this device. |
gcl_get_kernel_block_workgroup_info(square_kernel, |
CL_KERNEL_WORK_GROUP_SIZE, |
sizeof(wgs), &wgs, NULL); |
fprintf(stdout, "Workgroup size: %ld\n", wgs); |
// Finally, you can ask OpenCL for a workgroup size multiple. |
// This is a performance hint. |
gcl_get_kernel_block_workgroup_info(square_kernel, |
CL_KERNEL_PREFERRED_WORK_GROUP_SIZE_MULTIPLE, |
sizeof(preferred_wgs_multiple), |
&preferred_wgs_multiple, NULL); |
fprintf(stdout, "Preferred workgroup size multiple: %ld\n", |
preferred_wgs_multiple); |
// You can now use these workgroup values to craft an |
// appropriate cl_ndrange structure for use in launching |
// your kernel. |
}); |
dispatch_release(queue); |
} |
int main(int argc, const char* argv[]) { |
hello_world_sample1(); |
hello_world_sample2(); |
} |
Пример кода: получение размера рабочей группы ядра
В Перечислении 6-1 узел вызывает gcl_get_kernel_block_workgroup_info
метод в блоке на очереди отгрузки, создаваемой с OpenCL для запроса размера локальной памяти:
gcl_get_kernel_block_workgroup_info( |
square_kernel, |
CL_KERNEL_LOCAL_MEM_SIZE, |
sizeof(local_memsize), |
&local_memsize, NULL); |
Затем в перечислении 6-2, gcl_get_kernel_block_workgroup_info
функционируйте возвраты, что это считает оптимальным размером рабочей группы для этого ядра на этом устройстве:
gcl_get_kernel_block_workgroup_info( |
square_kernel, |
CL_KERNEL_WORK_GROUP_SIZE, |
sizeof(workgroup_size), &workgroup_size, NULL); |
fprintf(stdout, "Workgroup size: %ld\n", |
workgroup_size); |
Наконец, узел вызывает gcl_get_kernel_block_workgroup_info
функция для предложения размера рабочей группы, многократного на основе возможностей базового устройства:
gcl_get_kernel_block_workgroup_info( |
square_kernel, |
CL_KERNEL_PREFERRED_WORK_GROUP_SIZE_MULTIPLE, |
sizeof(preferred_workgroup_size_multiple), |
&preferred_workgroup_size_multiple, NULL); |
Можно использовать возвращенные значения рабочей группы для обработки надлежащего cl_ndrange structure
использовать в запуске Вашего ядра.
cl_ndrange range = { |
1, // The number of dimensions to use. |
{0, 0, 0}, // The offset in each dimension. Want to process |
// ALL of the data, so all three offsets are 0. |
// Always pass an offset for each of the |
// three dimensions even though the workgroup |
// may have fewer than three dimensions. |
{NUM_VALUES, 0, 0}, // The global range—this is how many items |
// total in each dimension you want to |
// process. |
// Always pass the global range for each of the |
// three dimensions even though the workgroup |
// may have fewer than three dimensions. |
{workgroup_size, 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. So in this test case, |
// have NUM_VALUE/workgroup_size workgroups. |
// Always pass the workgroup size for each of the |
// three dimensions even though the workgroup |
// may have fewer than three dimensions. |
}; |