Программирование CUDA (4): управление памятью

Память

Базовые знания о памяти.

Вообще говоря, регистры — кэши — основная память — дисковая память, скорость постепенно уменьшается, а емкость постепенно увеличивается.
вставьте сюда описание изображения
Память можно разделить на программируемую память и непрограммируемую память. Программируемая память означает, что пользователи могут читать и записывать эту память, в то время как непрограммируемая память относится к памяти, которая не открыта для пользователей, чтобы ускорить процесс.

В структуре памяти CPU и GPU кэш первого уровня и второго уровня (Cache) являются непрограммируемыми запоминающими устройствами.

Структура памяти графического процессора

У каждого Потока есть своя сумма , resigtersи local memoryу каждого Блока она есть.Все Потоки в этом Блоке могут получить к нему доступ, и между Гридами shared memoryбудут constant memory, texture memory, Global memoryи т.д., и все Сетки могут получить к нему доступ. CacheРазличные типы памяти имеют разные области действия, время жизни и поведение кэша.

Память Расположение Следует ли кэшировать разрешение на доступ переменное время жизни
регистр Чип никто устройство чтения/записи То же, что нить
локальная память на борту никто устройство чтения/записи То же, что нить
Общая память Чип никто устройство чтения/записи то же, что блок
постоянная память на борту иметь устройство только для чтения, хост для чтения/записи можно сохранить в программе
текстурная память на борту иметь устройство только для чтения, хост для чтения/записи можно сохранить в программе
глобальная память на борту никто чтение/запись устройства, чтение/запись хоста можно сохранить в программе

Зарегистрируйтесь

Регистры — это самое быстрое пространство памяти.В отличие от ЦП, у графических процессоров больше резервов регистров. Когда мы объявляем переменную без модификации в функции ядра, переменная сохраняется в регистре, и массиву с постоянной длиной, определенной в функции ядра, также назначается адрес в регистре.

Регистры являются частными для каждого потока. В регистрах обычно хранятся часто используемые частные переменные. Жизненный цикл регистровых переменных такой же, как и у функций ядра, от начала операции до конца операции.

Регистры — дефицитный ресурс в SM.В архитектуре Fermi на поток приходится до 63 регистров, а в архитектуре Kepler — до 255 регистров. Если поток использует меньше регистров, то резидентных блоков потока будет больше.Чем больше блоков параллельных потоков на SM, тем выше эффективность, выше производительность и коэффициент использования.Поэтому при программировании лучше использовать меньше регистров , Используйте меньше регистров.

Если в потоке слишком много переменных, так что регистров на всех не хватит, а регистры в это время переполнятся, локальная память поможет сохранить лишние переменные, что очень негативно скажется на эффективности.

локальная память локальная память

Переменные в функции ядра, которые хранятся в регистрах, но не могут войти в пространство регистров, выделенное функцией ядра, будут храниться в локальном пространстве. Переменные, которые компилятор может хранить в локальной памяти, следующие:

  • Локальный массив, на который ссылается неизвестный индекс.
  • Большие локальные массивы или структуры, которые могут занимать много места в регистрах.
  • Любая переменная, которая не удовлетворяет квалификации регистра ядра.

Локальная память по существу находится в той же области хранения, что и глобальная память, и доступ к ней характеризуется высокой задержкой и низкой пропускной способностью.

Для устройств версии выше 2.0 локальная память хранится в кэше L1 каждого SM или в кэше L2 устройства.

общая память общая память

Память, которая использует следующие модификаторы в функции ядра, называется разделяемой памятью: __shared__.

Каждый SM имеет определенный объем разделяемой памяти, выделенной блоками потоков.Общая память является встроенной памятью.По сравнению с основной памятью скорость намного выше, то есть задержка низкая, а пропускная способность высокая. Он похож на кэш L1, но может быть запрограммирован. При использовании разделяемой памяти будьте осторожны, чтобы не уменьшить количество активных деформаций на SM из-за чрезмерного использования разделяемой памяти.

Разделяемая память объявляется в функции ядра, и ее жизненный цикл такой же, как и у блока потока.Когда блок потока начинает выполняться, выделяется разделяемая память этого блока, а когда блок потока завершает работу, разделяемая память память освобождается.

Общая память видна потокам внутри блока и недоступна для других блоков потоков, поэтому возникает проблема конкуренции, и связь также может выполняться через разделяемую память. Чтобы избежать конкуренции за память, вы можете использовать оператор синхронизации void __syncthreads();. Оператор эквивалентен точке препятствия каждого потока при выполнении блока потока. Когда все потоки в блоке выполняются до этой точки препятствия, следующее вычисление может выполняться. Однако частое использование повлияет на эффективность выполнения ядра.

Общая память делится на блоки памяти одинакового размера для высокоскоростного параллельного доступа.
банка: Это метод деления. В процессоре доступ к памяти заключается в доступе к определенному адресу для получения данных об адресе, но здесь он заключается в одновременном доступе к адресам ряда банков, получении всех данных по этим адресам и их логическом отображении. в разные банки. Аналогично управлению чтением из памяти.
Чтобы обеспечить одновременный доступ к памяти с высокой пропускной способностью, разделяемая память делится на блоки памяти (банки) одинакового размера, к которым можно обращаться одновременно. Таким образом, поведение при чтении и записи n адресов в памяти может быть реализовано в виде одновременной работы b независимых банков, так что эффективная пропускная способность увеличивается в b раз по сравнению с банком.
Если адреса памяти, запрашиваемые несколькими потоками, отображаются в один и тот же банк, то эти запросы становятся сериализованными (serialized). Аппаратное обеспечение разделит эти запросы на x последовательностей запросов без конфликтов, снизив пропускную способность в x раз. Но если все потоки в варпе обращаются к одному и тому же адресу памяти, будет сгенерирована широковещательная рассылка (бордкаст), и эти запросы будут выполнены одновременно. Устройства с вычислительной мощностью 2.0 и выше также имеют возможность многоадресной рассылки и могут одновременно отвечать на запросы некоторых потоков, обращающихся к одному и тому же адресу памяти в одном и том же варпе.

постоянная память постоянная память

Постоянная память находится в памяти устройства, каждый SM имеет выделенный кэш постоянной памяти, постоянное использование памяти __constant__.

Постоянная память объявляется вне функции ядра и объявляется в глобальной области видимости.Для всех устройств может быть объявлен только определенный объем постоянной памяти.Постоянная память объявляется статически и видна всем функциям ядра в той же единице компиляции. Постоянная память не может быть изменена функцией ядра после инициализации хостом.

память текстуры память текстуры

Память текстур — это вид памяти только для чтения в графическом процессоре.Она используется для привязки определенного сегмента глобальной памяти к текстурной памяти.Этот сегмент глобальной памяти обычно принимает форму одномерного массива CUDA/глобальной памяти, двумерного или трехмерный массив CUDA, а затем извлекать данные из глобальной памяти, читая память текстур (также известное как выборка текстур). По сравнению с глобальным доступом к памяти, требующим выравнивания и слияния, текстурная память имеет хороший эффект ускорения при невыровненном доступе и произвольном доступе.

глобальная память глобальная память

Глобальная память — это аппаратная оперативная память, независимая от ядра графического процессора, то есть то, что мы часто называем видеопамятью.Большая часть пространства памяти графического процессора — это глобальная память. Глобальная память — это самое большое пространство памяти на графическом процессоре, оно имеет наибольшую задержку и использует наиболее распространенную память. Глобальный относится к области действия и жизненному циклу. Обычно он определяется в коде на стороне хоста, а также может быть определен на стороне устройства. Однако требуются модификаторы. Пока он не уничтожен, он принадлежит к тому же жизненному циклу. цикл как приложение.

кеш-кэш

Кэш графического процессора представляет собой непрограммируемую память, и на графическом процессоре существует 4 типа кешей:

  • Кэш L1
  • Кэш L2
  • постоянный кэш только для чтения
  • кэш текстур только для чтения

Каждый SM имеет кэш-память первого уровня, а все SM совместно используют кэш-память второго уровня. Функции кэшей первого и второго уровня используются для хранения данных в локальной и глобальной памяти, в том числе данных о переполнении регистров. Каждый SM имеет кэш констант только для чтения и кэш текстур только для чтения, которые используются в памяти устройства для повышения производительности чтения из соответствующих областей памяти.

В отличие от ЦП, процесс чтения и записи ЦП может кэшироваться, но процесс записи графического процессора не кэшируется, кэшируется только процесс чтения.

Распределение, освобождение и передача памяти графического процессора

Программы CUDA будут использовать память GPU и память CPU, выделение и освобождение памяти CPU могут использовать new и delete (C++), malloc, calloc и free (C). Выделение и освобождение памяти GPU реализовано с помощью библиотечных функций, предоставляемых CUDA. В то же время, поскольку две памяти не зависят друг от друга, для реализации передачи необходимо скопировать данные в разные памяти.

выделение данных памяти

\\ 分配设备上的内存。
cudaError_t cudaMalloc(void** devPtr, size_t size)

cudaMallocЭта функция используется для выделения памяти на устройстве и должна вызываться хостом (то есть вызываться в коде, выполняемом ЦП). Его возвращаемое значение cudaError_tявляется перечисляемым типом, который перечисляет все возможные ошибочные ситуации. И если вызов функции успешен, она возвращает cudaSuccess. Тип первого параметра — void **, указывающий на первый адрес выделенной памяти. Тип второго параметра — size_t, который указывает размер выделяемой памяти в байтах.

Освобождение данных памяти

\\ 释放先前在设备上申请的内存空间。
cudaError_t cudaFree(void* devPtr)

cudaFreeЭта функция используется для освобождения ранее выделенного пространства памяти на устройстве, но не может освободить память, выделенную с помощью malloc. Тип возвращаемого значения все еще cudaError_t. Параметр функции указывает на первый адрес памяти устройства, который необходимо освободить.

передача данных памяти

Для завершения синхронной передачи данных между памятью хоста и памятью устройства необходимо использовать cudaMemcpyфункцию:

\\ 数据同步拷贝。
cudaError_t cudaMemcpy(void* dst, const void* src, size_t count, cudaMemcpyKind kind)

Для завершения асинхронной передачи данных между памятью хоста и памятью устройства необходимо использовать cudaMemcpyAsyncфункцию:

\\ 数据异步拷贝
cudaError_t cudaMemcpyAsync(void* dst, const void* src, size_t count, cudaMemcpyKind kind, cudaStream_t stream = 0)

Если поток не равен нулю, он может перекрываться с другими операциями потока.

cudaMemcpyKindУказывает направление передачи данных, возможны следующие варианты:

  • cudaMemcpyHostToHost
  • cudaMemcpyHostToDevice
  • cudaMemcpyDeviceToHost
  • cudaMemcpyDeviceToDevice

обработка ошибок

Поскольку почти каждая функция API CUDA возвращает значение типа cudaError_t, оно используется для указания, был ли вызов функции успешным. Когда возвращаемое значение равно cudaSuccess, вызов функции выполнен успешно. В случае сбоя возвращаемое значение будет помечать конкретный код сбоя, и программист может получить конкретное сообщение об ошибке с помощью функции cudaGetErrorString. Поэтому для повышения надежности программы без потери красоты кода и облегчения исправления ошибок рекомендуется использовать GPUAssert()макрофункции.
Например:

GPUAssert(cudaMalloc(&dev_a, sizeof(int)));

Supongo que te gusta

Origin blog.csdn.net/weixin_43603658/article/details/129912525
Recomendado
Clasificación