Введение
Здравствуйте, дорогие читатели.
В данном посте я постараюсь рассмотреть особенности выделения памяти для объектов OpenCL.
OpenCL является кросс-платформенным стандартом гетерогенных вычислений. Не секрет, что на нём пишут программы тогда, когда от них требуется скорость выполнения. Как правило, подобный код нуждается во всесторонней оптимизации. Всякий GPGPU-разработчик знает, что операции с памятью зачастую являются самым слабым звеном в скорости работы программы. Так как в природе существует великое множество аппаратных платформ, поддерживающих OpenCL, то вопрос организации объектов памяти зачастую становится головной болью. То, что хорошо работает на Nvidia Tesla, оснащённых локальной памятью и соединённых широкой шиной с глобальной, отказывается показывать приемлемую производительность на SoC, имеющих совершенно иную архитектуру.
Об особенностях выделения памяти для систем с общей памятью CPU и GPU и пойдёт речь в данном посте. Использование типов памяти Image оставим в стороне и сосредоточимся на наиболее общеупотребительном типе Buffer. В качестве стандарта будем рассматривать версию 1.1, как наиболее распространённую. В начале проведём краткий теоретический курс, а затем рассмотрим несколько примеров.
Теория
Память выделяется при помощи вызова функции API clCreateBuffer. Синтаксис следующий:
cl_mem clCreateBuffer (
cl_context context,
cl_mem_flags flags,
size_t size,
void *host_ptr,
cl_int *errcode_ret)
Нас интересуют в первую очередь флаги, отвечающие за то, как именно будет выделена память. Допустимы следующие значения:
CL_MEM_READ_WRITEб CL_MEM_WRITE_ONLY, CL_MEM_READ_ONLY
Самый простой вариант. Память будет выделена на стороне OpenCL Device в режиме чтение-запись/только запись/только чтение.
CL_MEM_ALLOC_HOST_PTR
Память для объекта будет выделена из памяти Host’а – т. е. из оперативной памяти. Этот флаг представляет интерес для систем с общей памятью CPU и GPU.
CL_MEM_USE_HOST_PTR
Объект будет использует уже выделенную (и используемую программой) память Host’а по указанному адресу. Стандарт допускает возможность выделения памяти на стороне Device’а в качестве промежуточного буффера. Данный флаг и CL_MEM_ALLOC_HOST_PTR являются взаимоисключающими. Этот флаг интересен тем в том случае, если вы добавляете поддержку OpenCL в готовое приложение, и хотите использовать существую память для работы с ней на стороне Device.
CL_MEM_COPY_HOST_PTR
Данный флаг означает, что при создании объекта, будет произведён аналог memcpy с указанного адреса.
Практика
Попытаемся на практике выяснить, какой вариант выделения памяти лучше подходит для традиционного случая дискретной видеокарты с собственной памятью «на борту» и той, в которой GPU использует память из RAM. В качестве тестовых систем будут фигурировать следующие компьютеры:
- Система с дискретным видеочипом: Intel Core i5 4200U, 4Gb DDR31600Mhz, Radeon 8670M 128bit GDDR3 1800Mhz
- Система со встроенным видеочипом: AMD A6700, 8Gb DDR31800Mhx, Radeon 7660D 128bit
Операционная система в обоих случаях Windows7 SP1, среда разработки — Visual Studio 2013 Express + AMD APP SDK 2.9.
В качестве тестовой нагрузки будем производить чтение/запись и mapping/unmapping объектов памяти различного размера – от 65 Кб до 65Мб.
Недолго думая, перейдём к графикам. Во всех случаях ось абсцисс показывает объём памяти в байтах, ось ординат — время выполнения операции в микросекундах. Видеокарта с дискретной памятью помечена в названии графика как «discrete GPU», видеокарта с общей с CPU памятью помечена как «Integrated GPU»
Дискретная видеокарта
Данный график показывает линейную зависимость времени передачи данных от объёма. Адаптер использует собственную память, поэтому результаты стабильны.
В данном случае память для объекта была выделена из оперативной, поэтому имеем чуть больший разброс значений.
Графики иллюстрируют mapping/unmapping буффера, выделенного из памяти GPU. Mapping – это процедура отображения участка памяти из адресного пространства Device’а в пространство Host’а. Unmapping — обратный процесс. Так как для GPU с собственной памятью эти адресные пространства физически различны, что для проведения отображения происходит чтение/запись во временные буфферы.
При использования существующей памяти, выделенной на стороне Host’а, разброс по времени получается более существенным. Причин для этого может быть много – выравнивание памяти по различным базовым значениям, конкурентная нагрузка на контроллер памяти, и многое другое.
Интегрированная видеокарта
В случае общей памяти Host’a и Device’a разброс результатов сильнее. Это легко объясняется необходимостью использования общей памяти и возросшей нагрузкой на контроллер.
При выделении памяти из RAM, объекты памяти Device’a и Host’a находятся в одном физическом адресном пространстве и различных виртуальных. Поэтому время преобразования адреса постоянно и не зависит от объёма объекта.
Однако, если выделять память для объекта из объёма памяти GPU, то зависимость времени выполнения от объёма объекта будет аналогична той, что наблюдается при использовании дискретной видеокарты с поправкой на возросший разброс результатов.
Опять же, если используется память, выделенная со стороны Host'a, то мы получаем околонулевое время выполнения операции, не зависящее от объема буффера.
Выводы
Стоит отметить следующие закономерности, выявленные в ходе проведения эксперимента:
- Использование видеокарты с дискретной памятью даёт результаты с меньшим разбросом. Это объясняется использованием раздельной памяти. Использование общей памяти, наоборот, приводит к большему разбросу показателей.
- В обоих случаях использование флага USE_HOST_PTR приводит увеличивает неравномерность получаемых результатов.
Несмотря на то, что локальная память дискретной видеокарты предпочтительнее для работы ядер OpenCL в режиме интенсивных обращений, использование общей памяти даёт в некоторых случаях возможность проводить mapping/unmapping за околонулевое время, не зависящее от объёма буффера.
Технику mapping'а можно использовать в обоих рассматриваемых случаях. На системе с общей памятью она даёт вышеописанные преимущества, на системе с дискретной видеокартой она просто работает за такое же линейное время, как и классическая read/write схема.
Автор: RomanArzumanyan