CUDA, Использование 2D и 3D массивов
В Интернете много вопросов о размещении, копировании, индексации и т. Д. 2d и 3d массивов в CUDA. Я получаю много противоречивых ответов, поэтому я пытаюсь собрать прошлые вопросы, чтобы посмотреть, смогу ли я задать правильные.
Первая ссылка: https://devtalk.nvidia.com/default/topic/392370/how-to-cudamalloc-two-dimensional-array-/
Проблема: выделение 2d массива указателей
Решение пользователя: использовать mallocPitch
"Правильное" неэффективное решение: используйте malloc и memcpy в цикле for для каждой строки (абсурдные издержки)
"Более правильное" решение: разбейте его на 1d массив "профессиональное мнение", один комментарий о том, что никто не следит за производительностью, не использует структуры 2D-указателей на GPU
Проблема: выделение места на хосте и передача его на устройство
Дополнительная ссылка: https://devtalk.nvidia.com/default/topic/398305/cuda-programming-and-performance/dynamically-allocate-array-of-structs/
Решение с использованием подссылки: кодирование структур на основе указателей на графическом процессоре - это плохой опыт, который крайне неэффективен, заключая его в одномерный массив.
Третья ссылка: выделение 2D массива в памяти устройства в CUDA
Проблема: выделение и передача 2d массивов
Решение пользователя: использовать mallocPitch
Другое решение: сгладить его
Четвертая ссылка: Как использовать 2D-массивы в CUDA?
Проблема: выделить и пересмотреть 2d массивы
Представленное решение: не показывает распределение
Другое решение: раздавить
Есть много других источников, в основном говорящих то же самое, но в нескольких случаях я вижу предупреждения о структурах указателей на GPU.
Многие люди утверждают, что правильный способ выделения массива указателей - это вызов malloc и memcpy для каждой строки, но функции mallocPitch и memcpy2D существуют. Эти функции как-то менее эффективны? Почему это не будет ответ по умолчанию?
Другой "правильный" ответ для двумерных массивов состоит в том, чтобы объединить их в один массив. Должен ли я просто привыкнуть к этому как факту жизни? Я очень привередливый в своем коде, и он мне не по вкусу.
Другое решение, которое я рассматривал, состояло в том, чтобы максимизировать матричный класс, который использует массив указателей 1d, но я не могу найти способ реализовать оператор двойной скобки.
Также по этой ссылке: Скопировать объект на устройство?
и ответ по подссылке: ошибка сегментации cudaMemcpy
Это немного сомнительно.
Классы, которые я хочу использовать со всеми CUDA, имеют 2/3-мерные массивы, и не слишком ли много времени для их преобразования в 1-мерные массивы для CUDA?
Я знаю, что много просил, но в итоге я должен привыкнуть к сжатым массивам как к реальному факту, или я могу использовать функции 2d allocate и copy без лишних затрат, как в решении, где alloc и cpy вызываются в for цикл?
1 ответ
Поскольку ваш вопрос составляет список других вопросов, я отвечу, составив список других ответов.
cudaMallocPitch / cudaMemcpy2D:
Во-первых, API CUDA Runtime работает как cudaMallocPitch
а также cudaMemcpy2D
на самом деле не задействуются ни распределения с двумя указателями, ни двумерные (с двойной подпиской) массивы. Это легко проверить, просто взглянув на документацию и отметив типы параметров в прототипах функций. src
а также dst
параметры являются параметрами одного указателя. Они не могут быть дважды подписаны или дважды разыменованы. Для дополнительного примера использования, вот один из многих вопросов по этому вопросу. Вот полностью проработанный пример использования. Другой пример, охватывающий различные понятия, связанные с cudaMallocPitch
/ cudaMemcpy2d
использование здесь. Вместо этого правильный способ думать об этом состоит в том, что они работают с распределенными распределениями. Кроме того, вы не можете использовать cudaMemcpy2D
передавать данные, когда базовое распределение было создано с использованием набора malloc
(или же new
или аналогичные) операции в цикле. Такая конструкция размещения данных хоста особенно плохо подходит для работы с данными на устройстве.
В общем, динамически размещаемый 2D случай:
Если вы хотите узнать, как использовать динамически размещенный 2D-массив в ядре CUDA (имеется в виду, что вы можете использовать доступ с двойной подпиской, например, data[x][y]
), то cuda
На странице информации о тегах содержится "канонический" вопрос, который здесь. Ответ, данный талониями, включает в себя правильную механику и соответствующие предупреждения:
- есть дополнительная, нетривиальная сложность
- доступ, как правило, будет менее эффективным, чем 1D, потому что для доступа к данным требуется разыменование 2 указателей вместо 1.
(обратите внимание, что выделение массива объектов, в котором объект (ы) имеет встроенный указатель на динамическое размещение, по сути совпадает с концепцией двумерного массива, и пример, который вы связали в своем вопросе, является разумной демонстрацией этого)
уплощение:
Если вы думаете, что должны использовать общий 2D-метод, тогда, пожалуйста, это не невозможно (хотя иногда люди борются с процессом!) Однако из-за дополнительной сложности и сниженной эффективности канонический "совет" здесь заключается в том, чтобы "сгладить" ваш метод хранения, и используйте "симулированный" 2D доступ. Вот один из многих примеров вопросов / ответов, обсуждающих "уплощение".
В общем, динамически размещаемый 3D корпус:
По мере того, как мы расширяем это до 3 (или выше!) Измерений, общий случай становится слишком сложным для обработки, IMO. Дополнительная сложность должна сильно мотивировать нас искать альтернативы. Общий случай с тройной подпиской включает 3 обращения к указателю до фактического извлечения данных, что еще менее эффективно. Вот полностью проработанный пример (2-й пример кода).
особый случай: ширина массива известна во время компиляции:
Обратите внимание, что это следует рассматривать как особый случай, когда размер (ы) массива (ширина, в случае двумерного массива или 2 из 3 измерений для трехмерного массива) известны во время компиляции. В этом случае, с помощью соответствующего определения вспомогательного типа, мы можем "проинструктировать" компилятор, как следует вычислять индексацию, и в этом случае мы можем использовать доступ с двойной подпиской со значительно меньшей сложностью, чем в общем случае, и нет потерь эффективности из-за погони за указателем. Только один указатель должен быть разыменован для извлечения данных (независимо от размерности массива, если во время компиляции для n-мерного массива известны n-1 измерения). Первый пример кода в уже упомянутом здесь ответе (первый пример кода) дает полностью проработанный пример этого в трехмерном случае, а здесь ответ дает двухмерный пример этого особого случая.
код хоста с двойной подпиской, код устройства с одной подпиской:
Наконец, еще одна методологическая опция позволяет нам легко смешивать двухмерный (с двойной подпиской) доступ в коде хоста, используя только 1D (односоставный, возможно, с "симулированным двухмерным" доступом) в коде устройства. Работающий пример этого здесь. Организовав базовое выделение как непрерывное выделение, а затем построив указатель "дерево", мы можем разрешить двукратно подписанный доступ на хосте и, тем не менее, легко передать фиксированное выделение устройству. Хотя в примере это не показано, этот метод можно было бы расширить для создания системы доступа с двойной подпиской на устройстве на основе плоского распределения и созданного вручную указателя "дерево", однако это будет иметь примерно те же проблемы. как общий метод динамического выделения 2D, приведенный выше: он будет включать в себя доступ с двойным указателем (с двойной разыменовкой), поэтому он менее эффективен, и есть некоторая сложность, связанная со построением "дерева" указателей, для использования в коде устройства (например, это будет требуют дополнительного cudaMemcpy
операция, наверное).
Из вышеперечисленных методов вам нужно будет выбрать тот, который соответствует вашему аппетиту и потребностям. Нет ни одной рекомендации, которая подходила бы для каждого возможного случая.