2016-05-14 3 views
0

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

Я изменил свой код на версию SSE, но скорость почти не изменилась. Мне интересно, пользуюсь ли я SSE неправильным способом ...

Этот код предназначен для назначения, когда нам не разрешено включать параметры оптимизации компилятора.

версия Нет SSE:

int get_freq(const float* matrix, float value) { 

    int freq = 0; 

    for (ssize_t i = start; i < end; i++) { 
     if (fabsf(matrix[i] - value) <= FLT_EPSILON) { 
      freq++; 
     } 
    } 

    return freq; 
} 

SSE версия:

#include <immintrin.h> 
#include <math.h> 
#include <float.h> 

#define GETLOAD(n) __m128 load##n = _mm_load_ps(&matrix[i + 4 * n]) 
#define GETEQU(n) __m128 check##n = _mm_and_ps(_mm_cmpeq_ps(load##n, value), and_value) 
#define GETCOUNT(n) count = _mm_add_ps(count, check##n) 

    int get_freq(const float* matrix, float givenValue, ssize_t g_elements) { 

     int freq = 0; 
     int i; 

     __m128 value = _mm_set1_ps(givenValue); 
     __m128 count = _mm_setzero_ps(); 
     __m128 and_value = _mm_set1_ps(0x00000001); 


     for (i = 0; i + 15 < g_elements; i += 16) { 
      GETLOAD(0); GETLOAD(1); GETLOAD(2); GETLOAD(3); 
      GETEQU(0); GETEQU(1); GETEQU(2); GETEQU(3); 
      GETCOUNT(0);GETCOUNT(1);GETCOUNT(2);GETCOUNT(3); 
     } 

     __m128 shuffle_a = _mm_shuffle_ps(count, count, _MM_SHUFFLE(1, 0, 3, 2)); 
     count = _mm_add_ps(count, shuffle_a); 
     __m128 shuffle_b = _mm_shuffle_ps(count, count, _MM_SHUFFLE(2, 3, 0, 1)); 
     count = _mm_add_ps(count, shuffle_b); 
     freq = _mm_cvtss_si32(count); 


     for (; i < g_elements; i++) { 
      if (fabsf(matrix[i] - givenValue) <= FLT_EPSILON) { 
       freq++; 
      } 
     } 

     return freq; 
    } 
+1

Почему вы заботитесь о производительности, векторный код еще не * правильно * пока. – EOF

+0

Май, пожалуйста, скажите мне, в чем ошибка? Я исправлю это. –

+1

Вы не сравниваете 'fabs (x) <= FLT_EPSILON', вы сравниваете' x! = 0.0f', что совсем не эквивалентно не-векторизованной версии. – EOF

ответ

1

Некоторые компиляторы довольно хорошо о выполнении оптимизации векторов. Вы проверили созданную сборку оптимизированной сборки обеих версий? Разве «наивная» версия не использует SIMD или другие методы оптимизации?

+0

Да ... Но из-за нашего требования к домашнему заданию все флаги оптимизации отключены в компиляторах. –

+2

@JenniferQ:/facepalm. Переменные '__m128' сохраняются/перезагружаются между операторами в выводе компилятора' -O0'. Оптимизирующий код для '-O0' имеет существенные отличия от фактически оптимизирующего кода для компиляции с хорошим asm с' -O2' или '-O3'. Я собирался сам посмотреть на asm (на godbolt) (https://godbolt.org/g/6aAfWu), но код в вашем вопросе даже не компилируется. например конфликтующие имена переменных, см. сообщения об ошибках в этой ссылке. –

+0

Это мое плохое. Я вырезал код из целой программы, но забыл что-то изменить ... Теперь все должно быть готово. –

4

Если вам нужно скомпилировать с -O0, сделайте как можно больше в одном заявлении. В нормальном коде int a=foo(); bar(a); скомпилируется в том же asm как bar(foo()), но в коде -O0 вторая версия, вероятно, будет быстрее, потому что она не сохраняет результат в память и затем перезагружает его для следующего оператора.

-O0 предназначен для получения наиболее предсказуемых результатов отладки, поэтому все хранится в памяти после каждого утверждения. Это явно ужасно для производительности.

Я написал a big answer a while ago для другого вопроса от кого-то другого с таким глупым назначением, как ваш, который требовал от них оптимизации для -O0. Некоторые из них могут помочь.


Не пытайтесь усердно выполнять это задание. Вероятно, большинство «трюков», которые вы выяснили, что ваш код работает быстрее с -O0, будет иметь значение только для -O0, но не имеет значения, если включена оптимизация.

В реальной жизни код обычно скомпилирован с clang или gcc -O2 как минимум, а иногда и -O3 -march=haswell или что-то еще, чтобы авто-векторизовать. (После того, как она отлажена, и вы готовы оптимизировать.)


Re: ваше обновление:

Теперь он собирает, и ужасный ASM от версии SSE можно увидеть. Я положил его on godbolt along with a version of the scalar code that actually compiles, too.

Скалярная версия выходит намного хуже. Это источник делает все в одном выражении, поэтому временные записи остаются в реестрах. Счетчик циклов по-прежнему находится в памяти, хотя, например, выделяет его, по крайней мере, на одну итерацию за 6 циклов на Хасуэлле. (См тег вики для оптимизации ресурсов.)


BTW, Векторизованных fabsf() легко, см Fastest way to compute absolute value using SSE. Это и SSE сравниваются меньше, чем должны делать трюк, чтобы дать вам ту же семантику, что и ваш скалярный код. (Но еще труднее получить -O0, чтобы не сосать).

Возможно, вам просто нужно вручную развернуть скалярную версию один или два раза, потому что -O0 слишком сильно засасывает.

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