2012-01-13 5 views
9

У меня есть большой кусок кода, часть тела которого содержит этот кусок кода:SSE медленнее, чем FPU?

result = (nx * m_Lx + ny * m_Ly + m_Lz)/sqrt(nx * nx + ny * ny + 1); 

который я векторизованы следующим образом (все это уже float):

__m128 r = _mm_mul_ps(_mm_set_ps(ny, nx, ny, nx), 
         _mm_set_ps(ny, nx, m_Ly, m_Lx)); 
__declspec(align(16)) int asInt[4] = { 
    _mm_extract_ps(r,0), _mm_extract_ps(r,1), 
    _mm_extract_ps(r,2), _mm_extract_ps(r,3) 
}; 
float (&res)[4] = reinterpret_cast<float (&)[4]>(asInt); 
result = (res[0] + res[1] + m_Lz)/sqrt(res[2] + res[3] + 1); 

Результат верно; Однако, мой сравнительный анализ показывает, что векторизация версия медленнее:

  • Не-Векторизованная версия занимает 3750 мса
  • векторизованная версию принимает 4050 мса
  • Установка result в 0 непосредственно (и удаление этой части кода полностью) уменьшает весь процесс до 2500 мс

Учитывая, что векторизованная версия содержит только один набор умножений SSE (вместо четырех отдельных умножений FPU), почему он медленнее? Является ли FPU действительно быстрее, чем SSE, или здесь есть смешающая переменная?

(я на мобильном Core i5.)

+7

Прошло некоторое время, так как я видел вопрос SSE на SO. Думаю, все возвращаются из отпуска.:) – Mysticial

+0

@Mysticial: LOL. xD – Mehrdad

ответ

15

Вы тратите много времени перемещения скалярных значений в/из SSE регистров с _mm_set_ps и _mm_extract_ps - это порождает много инструкций, время выполнения из которых далеко перевесит любую выгоду от использования _mm_mul_ps. Взгляните на сгенерированный вывод сборки, чтобы узнать, сколько кода генерируется в дополнение к одной команде MULPS.

Для правильной нумерации необходимо использовать 128-битные SSE-нагрузки и магазины (_mm_load_ps/_mm_store_ps), а затем использовать команды SSE shuffle для перемещения элементов внутри регистров, где это необходимо.

Еще один момент, который необходимо отметить: современные процессоры, такие как Core i5, Core i7, имеют два скалярных FPU и могут выдавать 2 умножения с плавающей запятой за такт. Потенциальное преимущество SSE для одинарной точности с плавающей запятой, следовательно, в лучшем случае составляет только 2x. Легко потерять большую часть/все это пособие в 2 раза, если у вас есть чрезмерные инструкции по ведению домашнего хозяйства, как это имеет место здесь.

+0

Да, не понял, что движущиеся значения между регистрами были настолько медленными! Я на самом деле пытался * избежать операций с памятью. Замечательно, спасибо большое! :) +1 – Mehrdad

+3

@Mehrdad Это медленно, потому что он перемещается между регистрами в разных доменах (SSE-FP против общих регистров). Обычно для междоменного перемещения данных обычно требуется штраф в размере 1-2 циклов. – Mysticial

+0

@Mysticial: Ooooo хорошая точка - я полностью забыл, что регистр FPU совсем немного отличается от регистра общего назначения. :) – Mehrdad

0

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

3

Есть несколько проблем:

  1. Вы не увидите много преимуществ от использования инструкций SSE в таких операциях, потому что инструкции SSE должны быть лучше на параллельных операциях (то есть, умножая несколько значений в в то же время). То, что вы сделали, является неправильным использованием SSE
  2. не устанавливайте значения, используйте указатель на 1-ое значение в массиве, но тогда ваши значения не находятся в массиве
  3. не извлекать и копировать значения в массив , Это также злоупотребление SSE. Результат должен быть в массиве.