GPU Coder™ предоставляет доступ к двум различным выделениям памяти (malloc) режимы, доступные в модели программирования CUDA ® ,cudaMalloc и cudaMallocManaged. cudaMalloc API применим к традиционно отдельным процессорам и глобальным запоминающим устройствам GPU. cudaMallocManaged применима к унифицированной памяти.
С точки зрения программиста, традиционная компьютерная архитектура требует, чтобы данные распределялись и совместно использовались между пространствами памяти CPU и GPU. Потребность в приложениях для управления передачей данных между этими двумя пространствами памяти увеличивает сложность. Унифицированная память создает пул управляемой памяти, совместно используемый ЦП и графическим процессором. Управляемая память доступна как для ЦП, так и для графического процессора через один указатель. Унифицированная память пытается оптимизировать производительность памяти путем переноса данных на нужное устройство, одновременно скрывая сведения о миграции из программы. Хотя унифицированная память упрощает модель программирования, она требует вызовов синхронизации устройств, когда данные, записанные на GPU, доступны на CPU. Кодер графического процессора вставляет эти вызовы синхронизации. Согласно NVIDIA ®, унифицированная память может обеспечить значительные преимущества в производительности при использовании CUDA 8.0 или при нацеливании на встроенное оборудование, такое как NVIDIA Tegra ®.
Чтобы изменить режим выделения памяти в приложении Кодер графического процессора, используйте Malloc Mode раскрывающийся список в разделе Дополнительные параметры - > Кодер графического процессора. При использовании интерфейса командной строки используйте MallocMode построить свойство конфигурации и установить для него значение 'discrete' или 'unified'.
Кодер GPU анализирует зависимость данных между разделами CPU и GPU и выполняет оптимизацию для минимизации количества cudaMemcpy вызов функции в сгенерированном коде. Анализ также определяет минимальный набор местоположений для копирования данных между CPU и GPU с помощью cudaMemcpy.
Например, функция foo имеет разделы кода, которые обрабатывают данные последовательно на CPU и параллельно на GPU.
function [out] = foo(input1,input2)
…
% CPU work
input1 = …
input2 = …
tmp1 = …
tmp2 = …
…
% GPU work
kernel1(gpuInput1, gpuTmp1);
kernel2(gpuInput2, gpuTmp1, gpuTmp2);
kernel3(gpuTmp1, gpuTmp2, gpuOut);
…
% CPU work
… = out
end
Неоптимизированная реализация CUDA потенциально может иметь несколько cudaMemcpy вызовы функций для передачи всех входов gpuInput1,gpuInput2и временные результаты gpuTmp1,gpuTmp2 между вызовами ядра. Потому что промежуточные результаты gpuTmp1,gpuTmp2 не используются вне графического процессора, они могут храниться в памяти графического процессора, что приводит к меньшему cudaMemcpy вызовы функций. Эти оптимизации повышают общую производительность созданного кода. Оптимизированная реализация:
gpuInput1 = input1; gpuInput2 = input2; kernel1<<< >>>(gpuInput1, gpuTmp1); kernel2<<< >>>(gpuInput2, gpuTmp1, gpuTmp2); kernel3<<< >>>(gpuTmp1, gpuTmp2, gpuOut); out = gpuOut;
Устранение избыточности cudaMemcpy вызовы, кодер GPU анализирует все виды использования и определения данной переменной и использует флаги состояния для минимизации. В этой таблице приведен пример исходного кода и его внешнего вида.
| Исходный код | Оптимизированный сгенерированный код |
|---|---|
A(:) = …
…
for i = 1:N
gB = kernel1(gA);
gA = kernel2(gB);
if (somecondition)
gC = kernel3(gA, gB);
end
…
end
…
… = C;
|
A(:) = …
A_isDirtyOnCpu = true;
…
for i = 1:N
if (A_isDirtyOnCpu)
gA = A;
A_isDirtyOnCpu = false;
end
gB = kernel1(gA);
gA = kernel2(gB);
if (somecondition)
gC = kernel3(gA, gB);
C_isDirtyOnGpu = true;
end
…
end
…
if (C_isDirtyOnGpu)
C = gC;
C_isDirtyOnGpu = false;
end
… = C; |
_isDirtyOnCpu флаг сообщает оптимизации памяти кодера GPU о подпрограммах, в которых данная переменная объявляется и используется либо на CPU, либо на затем GPU.