2013-06-08 6 views
4

A и B - векторы или длина N, где N может находиться в диапазоне от 20 до 200. Я хочу рассчитать квадрат расстояния между этими векторами, i.e. d^2 = || A-B ||^2.Как векторизовать расчет расстояний с использованием SSE2

До сих пор у меня есть:

float* a = ...; 
float* b = ...; 
float d2 = 0; 

for(int k = 0; k < N; ++k) 
{ 
    float d = a[k] - b[k]; 
    d2 += d * d; 
} 

Это, кажется, работает хорошо, за исключением того, что я профилированного мой код, и это узкое место (более 50% времени тратится только делает это). Я использую Visual Studio 2012, на Win 7, с этими вариантами оптимизации: /O2 /Oi /Ot /Oy-. Я понимаю, что VS2012 должен автоиндексировать этот цикл (используя SSE2). Однако, если я вставляю #pragma loop(no_vector) в код, я не получаю заметное замедление, поэтому я думаю, что цикл не является векторизованным. Компилятор подтверждает, что с этим сообщением:

info C5002: loop not vectorized due to reason '1105' 

Мои вопросы:

  1. Можно ли исправить этот код, чтобы VS2012 может векторизации это?
  2. Если нет, было бы целесообразно попытаться сами векторизовать код?
  3. Можете ли вы порекомендовать мне веб-сайт, чтобы узнать о кодировании SSE2?
  4. Есть ли какое-то значение N, ниже которого векторизация будет контрпродуктивной?
  5. Что такое reason '1105'?

ответ

4

Из MSDN documentation код ошибки 1105 означает, что компилятор не может определить, как уменьшить код до векторизованных инструкций. Для операций с плавающей запятой указывается, что вам нужно указать параметр/fp: fast, чтобы включить любые сокращения с плавающей запятой.

+0

+1 для опции/fp: fast - это приводит к векторизации кода. Благодаря! Однако ваш код (даже с/fp: fast) на самом деле занимает в два раза длиннее моего исходного кода (без/fp: fast). Новый []/delete [] является большим хитом. Замена выделенного кучи выделенным буфером, выделенным стеком, сокращает половину времени, чтобы вернуть время возврата туда, где я начал. Захватывание данных дважды - также большой успех. Оказалось, что лучше всего сохранить мой оригинальный код и использовать/fp: быстро для увеличения 2x. – Bull

+0

Также спасибо за страницу с кодом ошибки 1105 в MSDN, не уверен, почему я не смог ее найти (я действительно был на этой странице!). Если вы удалите свой код, я приму этот ответ. Мой исходный код с/fp: fast - мое предпочтительное решение. – Bull

+0

@ user2151446 Я удалил код, хотя в моем тестовом примере код из вашего примера не был векторизован, следовательно, добавлена ​​сложность. Я собираюсь задать вопрос об этом завтра. – masrtis

6

Это довольно просто реализовать это с помощью SSE встроенных функций:

#include "pmmintrin.h" 

__m128 vd2 = _mm_set1_ps(0.0f); 
float d2 = 0.0f; 
int k; 

// process 4 elements per iteration 
for (k = 0; k < N - 3; k += 4) 
{ 
    __m128 va = _mm_loadu_ps(&a[k]); 
    __m128 vb = _mm_loadu_ps(&b[k]); 
    __m128 vd = _mm_sub_ps(va, vb); 
    vd = _mm_mul_ps(vd, vd); 
    vd2 = _mm_add_ps(vd2, vd); 
} 

// horizontal sum of 4 partial dot products 
vd2 = _mm_hadd_ps(vd2, vd2); 
vd2 = _mm_hadd_ps(vd2, vd2); 
_mm_store_ss(&d2, vd2); 

// clean up any remaining elements 
for (; k < N; ++k) 
{ 
    float d = a[k] - b[k]; 
    d2 += d * d; 
} 

Обратите внимание, что если вы можете гарантировать, что a и b являются 16 байт выровнены, то вы можете использовать _mm_load_ps, а не _mm_loadu_ps, которые могут помочь производительности, особенно на более старых (до Nehalem) процессорах.

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

+1

Хороший ответ! Как будто я сделал бы это сам. Однако я бы предложил @ user2151446 убедиться, что вы выделили 'a' и' b' с выравниванием 16 байтов. Таким образом, вы можете использовать '_mm_load_ps' вместо' _mm_loadu_ps'. Это еще больше оптимизирует код. – oysteijo

+1

Спасибо. Это работало хорошо, хотя и с таким же быстродействием, как мой исходный код с опцией '/ fp: fast'. Если я укажу '/ fp: fast', важно поставить' #pragma loop (no_vector) 'во второй цикл, чтобы остановить VS, пытаясь его векторизовать и потеряв половину коэффициента усиления от векторизации первого цикла. – Bull

+0

@oysteijo: thanks - Я добавил примечание о выровненных нагрузках. –

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