Загружает ли использование SIMD основные регистры процессора? - PullRequest
0 голосов
/ 20 ноября 2018

Давайте представим, что у нас есть разработчик программного обеспечения, цель которого - достичь абсолютного максимума производительности процессора.В современных процессорах у нас много ядер, мы можем загружать данные в кэш для более быстрой обработки, а также у нас есть инструкции SIMD (например, AVX), которые позволяют нам суммировать \ умножать \ делать другие операции с массивом элементов (умножить 8 целых чисел на один ЦП).Часы).Недостатком этой инструкции является стоимость отправки данных и инструкций в модуль SIMD + накладные расходы на преобразование векторного типа в примитивные типы (извините, я знаком только с вектором C #) (мы пока не будем зацикливаться на сложности кода).Насколько я понимаю, пока мы используем SIMD, основные регистры ЦП, используемые только для отправки и получения данных в эти регистры, и основные блоки АЛУ, используемые для вычислений общего назначения, в настоящее время простаивают.И вот мой вопрос - будет ли загрузка SIMD инструкций загружать основные блоки процессора?Например, если у нас огромное количество различных вычислений (давайте представим, что 40% из них лучше всего выполнять на SIMD, а 60% лучше, как обычно), SIMD позволит нам таким образом повысить производительность: 100% производительности всех ядер + n% производительности SIMD?

Я задаю этот вопрос, потому что, например, с GPGPU мы можем использовать GPU для параллельных вычислений, а CPU в этом случае использовать только для отправки и получения данныхпоэтому он все время простаивает, и мы можем использовать его производительность для чувствительных к задержкам задач.

1 Ответ

0 голосов
/ 27 ноября 2018

Похоже, что это вопрос об исполнении заказа?Современные x64 имеют несколько исполнительных портов на процессоре, и каждый из них может отправлять новую инструкцию за такт (таким образом, на процессоре Intel SkyLake может параллельно выполняться около 8 операций процессора).Некоторые из этих портов обрабатывают загрузку / хранение памяти, некоторые обрабатывают целочисленную арифметику, а некоторые обрабатывают инструкции SIMD.

Так, например, вы можете иметь возможность отправлять 2 плавающих набора AVX, битовую операцию AVX, 2 загрузки AVX, одно хранилище AVX и пару битов арифметики указателей на регистры общего назначения водин цикл [вам придется ждать завершения операции - задержка].Таким образом, теоретически, до тех пор, пока в коде нет ужасающих цепочек зависимостей, с некоторой осторожностью вы должны поддерживать занятость каждого из этих портов (или, по крайней мере, это основная цель!).

Простое правило 1 : чем больше вы заняты портами исполнения, тем быстрее работает ваш код.Это должно быть само собой разумеющимся.Если вы можете держать 8 портов занятыми, вы делаете в 8 раз больше, чем если бы вы могли держать только 1 занятым.В общем, это в основном , не стоит беспокоиться о (да, всегда есть исключения из правила)

Простое правило 2 : Когдаиспользуются порты выполнения SIMD, ALU не становится внезапно бездействующим [Небольшая ошибка терминологии с вашей стороны: ALU - просто бит процессора, который выполняет арифметику.Вычисление для операций общего назначения выполняется на ALU, но также правильно называть SIMD-модуль ALU.Вы хотели спросить: выключаются ли части ЦП общего назначения при использовании SIMD-блоков?На что нет ответа ...] .Рассмотрим этот оптимизированный метод AVX2 (который не делает ничего интересного!)

#include <immintrin.h>
typedef __m256 float8;
#define mul8f _mm256_mul_ps

void computeThing(float8 a[], float8 b[], float8 c[], int count)
{
    for(int i = 0; i < count; ++i)
    {
        a[i] = mul8f(a[i], b[i]);
        b[i] = mul8f(b[i], c[i]);
    }
}

Поскольку нет никаких зависимостей между a, b и c (о которых я действительно должен быть явным, указав __restrict), тогда два SIMDИнструкции умножения могут быть отправлены за один тактовый цикл (поскольку есть два исполнительных порта, которые могут обрабатывать умножение с плавающей запятой) .

АЛУ общего назначения здесь не отключается внезапно - регистры и инструкции общего назначения все еще используются!1. вычислить адреса памяти (для: a [i], b [i], c [i], d [i]) 2. загрузить / сохранить в этих ячейках памяти 3. увеличить счетчик циклов 4. проверитьесли счет был достигнут?

Просто так получается, что мы также используем блоки SIMD для нескольких умножений ...

Простое правило 3 : Для операций с плавающей запятой использование 'float' или '__m256' практически не имеет значения.Те же аппаратные средства ЦП, которые использовались для вычисления типов float или float8, абсолютно одинаковы.В кодировке машинного кода есть просто пара битов, которые определяют выбор между float / __m128 / __m256.

т.е. https://godbolt.org/z/xTcLrf

...