Эффективное матричное векторное умножение 4x4 с SSE: горизонтальное сложение и точка - какой смысл?

Я пытаюсь найти наиболее эффективную реализацию умножения матрицы 4x4 (M) на вектор (u) с использованием SSE. Я имею в виду Mu = v.

Насколько я понимаю, есть два основных способа сделать это:

    method 1) v1 = dot(row1, u), v2 = dot(row2, u), v3 = dot(row3, u), v4 = dot(row4, u)
    method 2) v = u1 col1 + u2 col2 + u3 col3 + u4 col4.

Способ 2 легко реализовать в SSE2. Метод 1 может быть реализован либо с помощью горизонтальной инструкции сложения в SSE3, либо с помощью инструкции скалярного произведения в SSE4. Однако во всех моих тестах метод 2 всегда превосходит метод 1.

Единственное место, где у метода 1 было бы преимущество, - матрица 3х4, например, для аффинного преобразования. В этом случае последний точечный продукт не нужен. Но даже в этом случае метод 2 на матрице 4x4 быстрее, чем метод 1 на матрице 3x4. Единственный метод, который я нашел, который быстрее, чем метод 2 на матрице 4x4, - это метод 2 на матрице 4x3.

И что'Точка горизонтального сложения и инструкция точечного произведения? На самом деле инструкция точечного производства дает худшую производительность в этом случае. Может быть, это как-то связано с форматом данных? Если можноt определить, как упорядочена матрица, тогда необходима транспонирование, и в этом случае, возможно, метод 1 будет лучше?

Смотрите ниже для некоторого кода.

__m128 m4x4v_colSSE(const __m128 cols[4], const __m128 v) {
  __m128 u1 = _mm_shuffle_ps(v,v, _MM_SHUFFLE(0,0,0,0));
  __m128 u2 = _mm_shuffle_ps(v,v, _MM_SHUFFLE(1,1,1,1));
  __m128 u3 = _mm_shuffle_ps(v,v, _MM_SHUFFLE(2,2,2,2));
  __m128 u4 = _mm_shuffle_ps(v,v, _MM_SHUFFLE(3,3,3,3));

  __m128 prod1 = _mm_mul_ps(u1, cols[0]);
  __m128 prod2 = _mm_mul_ps(u2, cols[1]);
  __m128 prod3 = _mm_mul_ps(u3, cols[2]);
  __m128 prod4 = _mm_mul_ps(u4, cols[3]);

  return _mm_add_ps(_mm_add_ps(prod1, prod2), _mm_add_ps(prod3, prod4));
}

__m128 m4x4v_rowSSE3(const __m128 rows[4], const __m128 v) {
  __m128 prod1 = _mm_mul_ps(rows[0], v);
  __m128 prod2 = _mm_mul_ps(rows[1], v);
  __m128 prod3 = _mm_mul_ps(rows[2], v);
  __m128 prod4 = _mm_mul_ps(rows[3], v);

  return _mm_hadd_ps(_mm_hadd_ps(prod1, prod2), _mm_hadd_ps(prod3, prod4));
}

__m128 m4x4v_rowSSE4(const __m128 rows[4], const __m128 v) {
  __m128 prod1 = _mm_dp_ps (rows[0], v, 0xFF);
  __m128 prod2 = _mm_dp_ps (rows[1], v, 0xFF);
  __m128 prod3 = _mm_dp_ps (rows[2], v, 0xFF);
  __m128 prod4 = _mm_dp_ps (rows[3], v, 0xFF);

  return _mm_shuffle_ps(_mm_movelh_ps(prod1, prod2), _mm_movelh_ps(prod3, prod4),  _MM_SHUFFLE(2, 0, 2, 0));
}  

Ответы на вопрос(1)

Ваш ответ на вопрос