ОБНОВЛЕНО - Отметьте ниже
Сделаю это как можно короче. С радостью добавлю дополнительные сведения, если потребуется.
У меня есть код 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-код?
Спасибо за чтение :)
Подробнее см. Ниже:
Простая векторная структура
_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;
};