Я предлагаю использовать побитовое и с маской. Положительные и отрицательные значения имеют одинаковое представление, отличаются только самые значащие биты, это 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. Цикл слишком мал, и накладные расходы на распределение задания между несколькими потоками могут быть больше, чем любая потенциальная выгода.
person
Norbert P.
schedule
13.05.2011