OpenCL (от англ. Open Computing Language — открытый язык вычислений) — фреймворк для написания компьютерных программ, связанных с параллельными вычислениями на различных графических (англ. GPU) и центральных процессорах (англ. CPU). В фреймворк OpenCL входят язык программирования, который базируется на стандарте C99, и интерфейс программирования приложений (англ. API). OpenCL обеспечивает параллелизм на уровне инструкций и на уровне данных и является реализацией техники GPGPU. OpenCL является полностью открытым стандартом, его использование не облагается лицензионными отчислениями. [http://ru.wikipedia.org/wiki/OpenCL]
Cтандарт OpenCl 1.2, однако, по сравнению с первым стандартом OpenCL 1.0, отличается лишь улучшениями с внутренностейей фреймворка, практически не касающихся разработчика.
Из материалов ATI по их реализации OpenCL были получены следующие сведения:
1. Для ATI Radeon HD 4870: 10 SIMD * 2 waves * 64 element = 1280 elements (minimum elements count for decrease memory latency)
На практике лучше использовать: 2560 вычислительных ядер, или 2560 * 2
2. Размер одного WaveFront-а - 64 элемента. Паковать свои ядра лучше в этот размер. Запросы на получение данных из памяти
скрываются переключением wavefront-ов
3. Пытаться получать int4/float4. Для получение данных отдавать предпочтение получению 128 битных данных.
4. Обеспечивать более чем один выход для одного work-item-а
5. Улучшать алгоритм
6. Размер рабочей группы кратен 64
7. Общее количество work-item – ов равно, по крайней мере, 2560
8. Иногда проще/лучше что-то пересчитывать, чем хранить. Тем более если для этого требуются ядра, которые не являются
влияющими на производительность.
9. Отдавать предпочтение простому паттерну доступа к данным как к линейному массиву.
В случае матриц осуществлять доступ по строчкам.
10. Для CPU совет - использовать больше работы в WorkItem-е
11. Советы по оптимизации по работе с CPU аналогичны GPU. К примеру совет по 128 битным данным сохраняется в виду
использования SSE.
12. На один(1) доступ в глобальную память осуществлять около восьми(8) арифметических операций.
13. Использовать арифметику, основанную на векторах
14. Использовать OpenCL Image для структур небольшого размера, в которых тяжело предсказать паттерн доступа.
15. Количество рабочих групп должно быть больше, чем количество Stream Multi Processor-ов.
(для того, чтобы все SM были заняты).
А желательно больше удвоенного или учетверенного количества этих SM. Количество work-item на рабочую группу должно быть
кратно (32(nvidia), 64(amd)), чем больше, тем лучше – это позволяет скрыть латентность обращения к памяти.
16. Добавлять суффикса f к константным переменным.
17. Возможно применить флаги компиляции OpenCL ядер –cl-mad-enable, -cl-fast-relaxed-math
18. Если в ядре в условии if фигурирует только значение local_id/warp_size – то всё нормально и гарантировано не будет проблем с
ожиданием выполнения ядер друг другом.
19. Вместо функции 1/sqrt(x) лучше вызывать rsqrt(x)
20. В случае доступа к памяти:
- время доступа к локальной памяти приблизительно равно 8 тактам
21. Вместо использования random-access доступа к глобальной памяти, лучше использовать изображения и/или локальную память
Stream Multiprocessor-а
22. Иногда выгоднее что-то пересчитать, чем хранить
23. Один большой запрос на доступ в память лучше, чем много маленьких.
Пытаться слить доступ в глобальную память потоков из одного WaveFront/Warp.
Для NVIDIA
1. Все нити полу-warp-а обращаются к 32/64 битным словам. Давая в результате 64/128 байтны блок (HalfWarp = 16)
2. Адрес получаемого блока кратен 64 или 128 соответственно
3. Все 16 слов лежат последовательно
4. Обращение к данным последовательно (из k-го потока half-warp к k-му слову) (compute capability 1.0, 1.1. В compute capability
1.2, 1.3 паттерн доступа может быть любой)
Пытаться избежать конфликта банков локальной памяти. (Локальная память для NVIDIA делится на 16 банков. Вся локальная
память последовательно разделенная на 32 бита относится к разным банкам памяти. Любой запрос чтения/записи в один банк
сериализуется.)
24. Каждый Stream Multiprocessor содержит
- 8192 или 16384(Tesla архитектура), 32 K(Fermi архитектура) 32ух битных значений (NVIDIA) (регистровый файл)
- 256 КБ (64-ех битных значений) (AMD) (регистровый файл)
- Объем разделяемой памяти обычно равен 16 КБ.
Количество используемых ядром регистров и размер локальной памяти на блок влияет на количестве блоков, которое выполняет
один мультипроцессор.
25. Каждый Stream Multiprocessor может одновременно располагать в себе следующее количество нитей:
Nvidia: 768(СС 1.0, 1.1), 1024(CC 1.2, 1.3), 1536(CC 2x), 2048 (СС 3x)
AMD: 32 * 64 = 2048 нитей ([13] 4.8.2 Resource Limits on Active Wavefronts.)
А точнее 8 work group-ов по 256 thread-а. Есть также ограничение на количество активных нитей на Compute Unit и на всё
устройство. ) ([14], D - 2.) Разработчику тяжело влиять на эти параметры.
26. Использовать по возможности не if/else инструкции, а оператор ?:
27. Привести используемый паттерн доступа к памяти к почти линейному.
28. Для видеокарт NVIDIA с Compute Capability 1.x только одно ядро/функция может одновременно выполняться на устройстве.
Для устройств с compute capability 2.x и выше использование всех устройств может быть достигнуто за счёт использования
нескольких очередей к устройству
29. Минимизация перемещения данных для низкоскоростных шин (для повышения пропускной способности (Throughput))
30. Использование локальной памяти. Стандартный паттерн:
а. Загрузить данные из глобальной памяти в локальную память
б. Синхронизация потоков в группе для того, чтобы удостовериться, что память может безопасно читаться другими потоками
в. Выполнить основной код
г. Синхронизировать потоки, чтобы удостовериться, что локальная память записана корректно.
д. Записать результаты обратно в глобальную память
31. Использование native_* функций, там где не требуется точность, использование float вместо double, использование mad
инструкции, сокращение количества инструкций.
32. Учитывать разные скорости доступа к данным:
- GPU-CPU через Pci-e, x16 ~ 8GB/s
- Global memory to Global Memory in GPU ~ 141 GB/s
Таким образом лучше минимизировать передачу данных CPU <> GPU.
Один большой запрос в память лучше, чем много маленьких.
33. Все OpenCL функции потокобезопасны кроме clSetKernelArg, при вызове которой следует учитывать разные скорости
доступа к данным
Ati Stream SDK OpenCL Programming Guide
NVIDIA OpenCL Programming Guide