Для вектора SSE, который имеет все те же компоненты, генерируется "на лету" или "прекомпуте"?

Когда мне нужно выполнить векторную операцию, которая имеет операнд, который является просто поплавком, передаваемым каждому компоненту, должен ли я прекомпотировать __m256 или __m128 и загружать его, когда мне это нужно, или передавать поплавок в регистр с помощью _mm_set1_ps каждый раз, когда мне нужен вектор?

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

Выполняется ли _mm_set1_ps с помощью одной команды? Это может ответить на мой вопрос.

Ответ 1

Естественно, это сильно зависит от вашего кода, но я реализовал две простые функции, используя оба подхода. См. код

__m128 calc_set1(float num1, float num2)
{
  __m128 num1_4 = _mm_set1_ps(num1);
  __m128 num2_4 = _mm_set1_ps(num2);
  __m128 result4 = _mm_mul_ps(num1_4, num2_4);

  return result4;
}

__m128 calc_mov(float* num1_4_addr,  float* num2_4_addr)
{
   __m128 num1_4 = _mm_load_ps(num1_4_addr);
  __m128 num2_4 = _mm_load_ps(num2_4_addr);
  __m128 result4 = _mm_mul_ps(num1_4, num2_4);

  return result4;
}

и сборка

calc_set1(float, float):
    shufps  $0, %xmm0, %xmm0
    shufps  $0, %xmm1, %xmm1
    mulps   %xmm1, %xmm0
    ret
calc_mov(float*, float*):
    movaps  (%rdi), %xmm0
    mulps   (%rsi), %xmm0
    ret

Вы можете видеть, что calc_mov() делает то, что вы ожидаете, и calc_set1() использует одну команду тасования.

Команда

A movps может принимать приблизительно четыре цикла для генерации адреса + больше, если порт загрузки кеша L1 занят + больше в редком случае промаха в кеше.

shufps займет один цикл на любой из последних микроархитектур Intel. Я верю, что это верно для SSE128 или AVX256. Поэтому я бы предложил использовать подход mm_set1_ps.

Конечно, команда shuffle предполагает, что float уже находится в регистре SSE/AVX. Если вы загружаете его из памяти, тогда трансляция будет лучше, так как она будет захватывать лучшие из movps и shufps в одной команде.

Ответ 2

Я считаю, что лучше всего отвлечь ваш вектор SSE от вашего кода (например, цикл) и использовать его всякий раз, когда вам нужно, предполагать, чтобы вы не случайно ввели его в память. (Например, если вы берете его адрес или передаете его ссылкой на другую функцию, тогда он может быть принудительно включен в память, и вы можете получить нечетное поведение.)
Идея состоит в том, что обычно лучше избегать переноса значений в регистры SSE и из них, и если это произойдет, то это не так в вашей конкретной ситуации, компилятор уже знает, как было построено значение, и может rematerialize при необходимости. Я думаю, что это намного проще, чем движение цикла с инвариантным движением в целом, что является обратной оптимизацией (т.е. Когда компилятор ставит под сомнение это для вас ) и который требует от компилятора доказать, что код действительно является петлевым инвариантом.

Ответ 3

Я играл с трансляциями для ответа на самый быстрый способ заполнить вектор (SSE2) определенным значением. Шаблоны дружественные. Посмотрите несколько отвалов трансляции в формате asm.

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

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

Я бы не использовал переменную static const, чтобы кэшировать ее между вызовами функции. (Это может привести к генерации кода компилятора, чтобы проверить, была ли переменная уже инициализирована при каждом вызове.)

Трансляции констант времени компиляции иногда приводят к трансляции во время компиляции, поэтому ваш код содержит только 16B данных const, которые находятся в памяти.

Передача AVX1 значения, уже имеющегося в регистре, является наихудшим. AVX1 предоставляет только источник памяти vbroadcastps (использует только порт загрузки). Передача принимает значение shufps / vinsertf128.

AVX2 требуется для vbroadcastps ymm, xmm (использует порт тасования)).