SSE работает медленно после использования AVX

У меня странная проблема с некоторыми SSE2 и кодом AVX, над которыми я работал. Я создаю свое приложение, используя GCC, которое обнаруживает функцию cpu. Объектные файлы создаются с отдельными флагами для каждой функции ЦП, например:

g++ -c -o ConvertSamples_SSE.o ConvertSamples_SSE.cpp -std=c++11 -fPIC -O0 -g -Wall -I./include -msse
g++ -c -o ConvertSamples_SSE2.o ConvertSamples_SSE2.cpp -std=c++11 -fPIC -O0 -g -Wall -I./include -msse2
g++ -c -o ConvertSamples_AVX.o ConvertSamples_AVX.cpp -std=c++11 -fPIC -O0 -g -Wall -I./include -mavx

Когда я впервые запускаю программу, я обнаружил, что подпрограммы SSE2 соответствуют нормальному с хорошим ускорением скорости по сравнению с процедурами без SSE (примерно на 100% быстрее). После запуска любой процедуры AVX точно такая же процедура SSE2 выполняется намного медленнее.

Может кто-нибудь объяснить причину этого?

До запуска процедуры AVX все тесты примерно на 80-130% быстрее, чем математика FPU, как можно видеть здесь, после выполнения процедуры AVX, процедуры SSE выполняются намного медленнее.

Если я пропущу тестовые процедуры AVX, я никогда не вижу эту потерю производительности.

Вот моя процедура SSE2

void Float_S16(const float *in, int16_t *out, const unsigned int samples)
{
  static float  ratio = (float)Limits<int16_t>::range() / (float)Limits<float>::range();
  static __m128 mul   = _mm_set_ps1(ratio);

  unsigned int i;
  for (i = 0; i < samples - 3; i += 4, in += 4, out += 4)
  {
    __m128i con = _mm_cvtps_epi32(_mm_mul_ps(_mm_load_ps(in), mul));
    out[0] = ((int16_t*)&con)[0];
    out[1] = ((int16_t*)&con)[2];
    out[2] = ((int16_t*)&con)[4];
    out[3] = ((int16_t*)&con)[6];
  }

  for (; i < samples; ++i, ++in, ++out)
    *out = (int16_t)lrint(*in * ratio);
}

И версия AVX того же самого.

void Float_S16(const float *in, int16_t *out, const unsigned int samples)
{
  static float ratio = (float)Limits<int16_t>::range() / (float)Limits<float>::range();
  static __m256 mul  = _mm256_set1_ps(ratio);

  unsigned int i;
  for (i = 0; i < samples - 7; i += 8, in += 8, out += 8)
  {
    __m256i con = _mm256_cvtps_epi32(_mm256_mul_ps(_mm256_load_ps(in), mul));
    out[0] = ((int16_t*)&con)[0];
    out[1] = ((int16_t*)&con)[2];
    out[2] = ((int16_t*)&con)[4];
    out[3] = ((int16_t*)&con)[6];
    out[4] = ((int16_t*)&con)[8];
    out[5] = ((int16_t*)&con)[10];
    out[6] = ((int16_t*)&con)[12];
    out[7] = ((int16_t*)&con)[14];
  }

  for(; i < samples; ++i, ++in, ++out)
    *out = (int16_t)lrint(*in * ratio);
}

Я также запускаю это через valgrind, который не обнаруживает ошибок.

Ответ 1

Смешивание кода AVX и устаревшего кода SSE приводит к снижению производительности. Наиболее разумным решением является выполнение инструкции VZEROALL после сегмента AVX кода, особенно перед выполнением кода SSE.

В соответствии с диаграммой Intel штраф при переходе в состояние C (или устаревший SSE с верхней половиной регистров AVX сохраняется) составляет порядка 100 тактов. Другие переходы - всего 1 цикл:

Литература: