2015-10-22 2 views
0

В OpenCL в действии Мэтью Скарпино комментирует, что переключение его префикс-суммы с 32-битного поплавка на 128-битный набор из 4 поплавков приводит к значительному улучшению производительности, почти в 4 раза Быстрее!Почему ожидается float4 выполнить float в префиксной сумме

При запуске приложения CH10/уменьшения, то он будет выполняться как reduction_scalar и reduction_vector ядра. В дополнение к проверке результатов , он измеряет время, затраченное на выполнение каждого ядра. На моей системой получены следующие результаты:

redu_scalar: проверка прошла. Общее время = 489031

уменьшение_vector: проверка прошла. Не Общее время = 136157

Matthew Scarpino

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

Мы используем метод, предложенный Дэвидом Lichterman, который обрабатывает восемь элементов на поток вместо двух загрузив два float4 элементов на поток, а не два поплавок элементов (Lichterman 2007).

http://http.developer.nvidia.com/GPUGems3/gpugems3_ch39.html

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

В качестве альтернативы каждый варп выдает запрос, а затем переходит в режим сна. В случае float4 он просыпается в 4 раза больше данных, но в случае float данные не кэшируются, поэтому последующие потоки также должны будут спать и ждать новых данных. С другой стороны. Я ожидаю, что память будет транслироваться по мере того, как потоки внутри варпа пробуждаются.

Мне интересно, могут ли некоторые эксперты прослушивать и предоставить последовательное объяснение, объясняют, почему float4 выполняет значительно быстрее, чем float, с точки зрения доступа к памяти или вычисления.

+1

Идея заключается в том, что я считаю, что для насыщения полосы пропускания памяти вам необходимо выдать запросы памяти максимальной ширины для вашего оборудования. Если вы выдаете индивидуальные запросы 1B (char) для каждого рабочего элемента, и если запросы «объединены» с CUDA способом с искажениями из 32 рабочих элементов, вы будете загружать только 32 * 1B за такт на каждый SM. Если вы выдаете отдельные запросы 4B (float), вы загружаете 32 * 4 = 256B за цикл на SM. И если вы используете данные 256B (float4), вы будете загружать 1024B на SM за цикл (при условии, что hw поддерживает его). – Gilles

+0

С более старой архитектурой VLIW от AMD вам нужно было использовать векторы для полного использования аппаратного обеспечения. Их новое оборудование GCN является скалярным, поэтому это больше не нужно. – Dithermaster

ответ

3

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

Для уточнения аспекта пропускной способности памяти, рассмотрит, например, случай GPU NVIDIA: аппаратные разработаны таким образом, что он может обеспечить 128 байт (от 128-байтового выравнивания сегмента а) в одной транзакции к деформироваться; это справедливо как для архитектур с кешем L1 (кэширование составляет 128 байт), так и в старых архитектурах без кеша. Следовательно, при использовании float s в более широком типе данных вы используете не более половины доступной полосы пропускания, без каких-либо причин.

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

Улучшение от float2 до float4 значительно менее резкое, но вы все равно увидите некоторые. Это связано с тем, что графические процессоры предназначены для загрузки 128-битных данных для рабочих элементов (подумайте о 4 проективных координатах или 4 цветовых компонентах (RGBA)). Аппаратно, это не более эффективно, чем загрузка 64-битных данных (вам обычно потребуется в два раза больше транзакций, чтобы фактически предоставить float4 с, чем вам необходимо было предоставить float2). Однако аппаратное обеспечение предназначено для ввода нагрузки 128-битных данных с помощью одной команды (что приводит к двум транзакциям за кулисами). Это более эффективно, особенно когда в цикле, поскольку это приводит к меньшему количеству итераций цикла для обработки одного и того же количества данных (помните, что в цикле следующая команда загрузки зависит от предыдущего увеличения индекса, поэтому вы эффективно избавившись от пары зависимых команд.)

Обратите внимание, что переход к float8 или float16 будет не улучшить ситуацию дальше, так как аппаратные средства не предназначен для тех типов данных (на GPU), а на самом деле эти виды нагрузок, в общем, разрушают коалесценцию или ухудшают использование кегля, что приводит к ухудшению производительности.

+0

>> Аппаратное обеспечение спроектировано так, что оно может обеспечить 128 байтов Я все еще смущен в этой точке, поэтому каждый раз, когда я прошу 32 бита, требуется то же самое время, что и запрос на 128 бит. Я хочу верить вам, но вы можете подробно рассказать об этом, может быть, источник или что-то еще? Также его биты правы? – Mikhail

+0

Нет, я на самом деле имею в виду _bytes_, и это байты, а не бит, потому что аппаратное обеспечение действует в терминах перекосов (или половинных искажений, в зависимости от поколения).
Так что, если каждый рабочий элемент запрашивает одно 32-битное значение (например, 'float'), контроллер будет отправлять 16 * 32-битные значения (по одному на один рабочий элемент в полушаге) в одной транзакции, если он может (где «он может» снова зависеть от генерации и шаблона доступа, и это то, что такое слияние). Это 64 _bytes_ в транзакции. – Oblomov

+0

(продолжение)
Если каждый рабочий элемент считывает 64-разрядный элемент, контроллер, если возможно, будет обслуживать полный полушаг в транзакции, а это 128 _bytes_. Если каждый рабочий элемент считывает 128-битный элемент, контроллер, по возможности, будет обслуживать полный полуотверстие в двух транзакциях.
Для графических процессоров с рабочим кэшем это еще проще: кэш-линии L1 составляют 128 _bytes_. Опять же, это связано с тем, что аппаратное обеспечение предназначено для мышления с точки зрения перекосов, а не отдельных рабочих элементов.
Детали для AMD меняются, но принципы практически одинаковы. – Oblomov

Смежные вопросы