ОБНОВЛЕНО - проверка ниже
Будет сохранять это как можно более коротким.Рад добавить больше деталей, если требуется.
У меня есть некоторый код 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.
Iдобавлена новая структура таймера, которая напрямую использует 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;
};