Профилирование SIMD-кода

ОБНОВЛЕНО - Отметьте ниже

Сделаю это как можно короче. С радостью добавлю дополнительные сведения, если потребуется.

У меня есть код sse для нормализации вектора. Я использую QueryPerformanceCounter () (завернутый во вспомогательную структуру) для измерения производительности.

Если я измеряю так

for( int j = 0; j < NUM_VECTORS; ++j )
{
  Timer t(norm_sse);
  NormaliseSSE( vectors_sse+j);
}

Результаты, которые я получаю, часто оказываются медленнее, чем простая нормализация с 4 двойными значениями, представляющими вектор (тестирование в той же конфигурации).

for( int j = 0; j < NUM_VECTORS; ++j )
{
  Timer t(norm_dbl);
  NormaliseDBL( vectors_dbl+j);
}

Однако синхронизация всего цикла, подобного этому

{
  Timer t(norm_sse);
  for( int j = 0; j < NUM_VECTORS; ++j ){
    NormaliseSSE( vectors_sse+j );
  }    
}

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

Например, я знаю, что могут быть штрафы при преобразовании результатов в плавающий, но ничего из этого не происходит здесь.

Может ли кто-нибудь подсказать? Что такого в вызове QueryPerformanceCounter между каждой нормализацией, который так сильно замедляет SIMD-код?

Спасибо за чтение :)

Подробнее см. Ниже:

  • Оба метода нормализации встроены (проверены при разборке)
  • Выполняется в выпуске
  • 32-битная компиляция

Простая векторная структура

_declspec(align(16)) struct FVECTOR{
    typedef float REAL;
  union{
    struct { REAL x, y, z, w; };
    __m128 Vec;
  };
};

Код для нормализации SSE:

  __m128 Vec = _v->Vec;
  __m128 sqr = _mm_mul_ps( Vec, Vec ); // Vec * Vec
  __m128 yxwz = _mm_shuffle_ps( sqr, sqr , 0x4e ); 
  __m128 addOne = _mm_add_ps( sqr, yxwz ); 
  __m128 swapPairs = _mm_shuffle_ps( addOne, addOne , 0x11 );
  __m128 addTwo = _mm_add_ps( addOne, swapPairs ); 
  __m128 invSqrOne = _mm_rsqrt_ps( addTwo ); 
  _v->Vec = _mm_mul_ps( invSqrOne, Vec );   

Код для нормализации двойников

double len_recip = 1./sqrt(v->x*v->x + v->y*v->y + v->z*v->z);
v->x *= len_recip;
v->y *= len_recip;
v->z *= len_recip;

Вспомогательная структура

struct Timer{
  Timer( LARGE_INTEGER & a_Storage ): Storage( a_Storage ){
      QueryPerformanceCounter( &PStart );
  }

  ~Timer(){
    LARGE_INTEGER PEnd;
    QueryPerformanceCounter( &PEnd );
    Storage.QuadPart += ( PEnd.QuadPart - PStart.QuadPart );
  }

  LARGE_INTEGER& Storage;
  LARGE_INTEGER PStart;
};

Обновление Итак, благодаря комментариям Джонса, я думаю, мне удалось подтвердить, что именно QueryPerformanceCounter плохо влияет на мой код simd.

Я добавил новую структуру таймера, которая напрямую использует RDTSC, и, похоже, дает результаты, соответствующие тому, что Я ожидал. Результат по-прежнему намного медленнее, чем синхронизация всего цикла, а не каждой итерации отдельно, но я ожидаю, что это потому, что получение RDTSC включает в себя очистку конвейера инструкций (см. http://www.strchr.com/performance_measurements_with_rdtsc ] для получения дополнительной информации).

struct PreciseTimer{

    PreciseTimer( LARGE_INTEGER& a_Storage ) : Storage(a_Storage){
        StartVal.QuadPart = GetRDTSC();
    }

    ~PreciseTimer(){
        Storage.QuadPart += ( GetRDTSC() - StartVal.QuadPart );
    }

    unsigned __int64 inline GetRDTSC() {
        unsigned int lo, hi;
        __asm {
             ; Flush the pipeline
             xor eax, eax
             CPUID
             ; Get RDTSC counter in edx:eax
             RDTSC
             mov DWORD PTR [hi], edx
             mov DWORD PTR [lo], eax
        }

        return (unsigned __int64)(hi << 32 | lo);

    }

    LARGE_INTEGER StartVal;
    LARGE_INTEGER& Storage;
};

8
задан JBeFat 28 April 2011 в 14:54
поделиться