Предлагаю использовать поразрядно и с маской.Положительные и отрицательные значения имеют одинаковое представление, отличается только старший значащий бит, это 0 для положительных значений и 1 для отрицательных значений, см. формат чисел двойной точности .Вы можете использовать один из них:
inline __m128 abs_ps(__m128 x) {
static const __m128 sign_mask = _mm_set1_ps(-0.f); // -0.f = 1 << 31
return _mm_andnot_ps(sign_mask, x);
}
inline __m128d abs_pd(__m128d x) {
static const __m128d sign_mask = _mm_set1_pd(-0.); // -0. = 1 << 63
return _mm_andnot_pd(sign_mask, x); // !sign_mask & x
}
Кроме того, было бы неплохо развернуть цикл, чтобы разорвать цепочку зависимостей, переносимых циклом.Поскольку это сумма неотрицательных значений, порядок суммирования не важен:
double norm(const double* sima, const double* simb) {
__m128d* sima_pd = (__m128d*) sima;
__m128d* simb_pd = (__m128d*) simb;
__m128d sum1 = _mm_setzero_pd();
__m128d sum2 = _mm_setzero_pd();
for(int k = 0; k < 3072/2; k+=2) {
sum1 += abs_pd(_mm_sub_pd(sima_pd[k], simb_pd[k]));
sum2 += abs_pd(_mm_sub_pd(sima_pd[k+1], simb_pd[k+1]));
}
__m128d sum = _mm_add_pd(sum1, sum2);
__m128d hsum = _mm_hadd_pd(sum, sum);
return *(double*)&hsum;
}
Развернув и разорвав зависимость (sum1 и sum2 теперь независимы), вы позволяете процессору выполнять сложения, которыепорядок.Поскольку инструкция конвейеризируется на современном процессоре, процессор может начать работу над новым дополнением до того, как закончится предыдущий.Кроме того, побитовые операции выполняются на отдельном исполнительном блоке, центральный процессор может фактически выполнять его в том же цикле, что и сложение / вычитание.Я предлагаю Руководства по оптимизации Agner Fog .
Наконец, я не рекомендую использовать openMP.Цикл слишком мал, и затраты на распределение работы между несколькими потоками могут быть больше, чем любая потенциальная выгода.