Поиск…


Вступление

В этом разделе представлены некоторые из основных основных механизмов параллельных вычислений, которые необходимы для полного понимания и использования OpenCL.

Темы и выполнение

Ключом параллелизма является использование нескольких потоков для решения проблемы (duh.), Но есть некоторые отличия от классического многопоточного программирования в том, как организованы потоки.

Сначала давайте поговорим о вашем типичном графическом процессоре, для простоты я сосредоточусь на

Графический процессор имеет много процессорных ядер, что делает его идеальным для одновременного выполнения множества потоков. Эти ядра организованы в потоковых процессорах (SM, термин NVidia), из которых GPU имеет заданное число.

Все потоки, запущенные внутри SM, называются «блоком потоков». На SM может быть больше потоков, чем в ядрах. Количество ядер определяет так называемый «размер Warp» (термин NVidia). Нити внутри блока потока разделены на так называемые «перекосы».

Быстрый пример для отслеживания: типичный NVidia SM имеет 32 процессорных ядра, поэтому размер его основы равен 32. Если в моем потоковом блоке теперь будет выполняться 128 потоков, они будут сбрасываться в 4 перекоса (4 перекоса * 32 размера основы = 128 потоки).

Размер основы очень важен при выборе количества потоков позже.

Все потоки внутри одной основы делят один счетчик команд. Это означает, что эти 32 потока действительно синхронизированы в том, что каждый поток выполняет каждую команду одновременно. Здесь лежит ошибка производительности: это также относится к ответвлениям в вашем ядре!

Пример. У меня есть ядро ​​с инструкцией if и двумя ветвями. 16 из моих потоков внутри warp будут выполнять ветвь один, остальные 16 ветви два. До выражения if все потоки внутри warp синхронизируются. Теперь половина из них выбирает другую ветку. Что происходит, так это то, что другая половина останется бездействующей до тех пор, пока неправильный оператор не завершит выполнение первых 16 потоков. Затем эти потоки будут спящими до тех пор, пока остальные 16 потоков не закончат свою ветку.

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

Синхронизация потоков также не является простым делом. Вы можете синхронизировать потоки только с одним SM. Все, что находится за пределами SM, не поддается проверке внутри ядра. Вам придется писать отдельные ядра и запускать их один за другим.

Память графического процессора

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

  • Глобальная память: самая большая доступная память и одна из немногих для обмена данными с хостом. Эта память имеет наивысшую задержку и доступна для всех потоков.
  • Constant Memory (постоянная память): чтение только части глобальной памяти, которая может быть прочитана только другими потоками. Его преимуществом является более низкая латентность по сравнению с глобальной памятью
  • Память текстуры: также часть постоянной памяти, специально предназначенная для текстур
  • Общая память: эта область памяти расположена близко к SM и может быть доступна только одному блоку потока. Он предлагает более низкую задержку, чем глобальная память, и немного меньше латентности, чем постоянная память.
  • Регистры: доступны только для одного потока и самой быстрой памяти из всех них. Но если компилятор обнаруживает, что для нужного ядра недостаточно регистров, он переносит переменные в локальную память.
  • Локальная память: доступная только для потока часть памяти в глобальной области памяти. Используется в качестве резервной копии для регистров, которых следует избегать, если это возможно.

Доступ к памяти

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

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

Различные воспоминания организованы в так называемых «банках». Каждый запрос памяти для банка может обрабатываться за один такт. Количество банков в общей памяти равно размеру основы. Скорость памяти может быть увеличена за счет избежания конфликта банковского доступа внутри одной основы.

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



Modified text is an extract of the original Stack Overflow Documentation
Лицензировано согласно CC BY-SA 3.0
Не связан с Stack Overflow