Jin X, > Почему плавает? Тема этого счётчика древняя как мамонты, посмотри поиском, не особо давно разбирали. > fld Дело в том что это не чистый замер. Это под обработкой ядра, в частности планировщика и ловушек. Большую часть NPX обрабатывает ядро, часть симулирует. Постоянно сбрасывает хард маркеры на новых квантах, оптимизации ради, так что не удивляйся когда при тестах тайминг плавает - ты не выключил(исключил) из статистики планировщик и саму ось в целом. > Вообще, хотелось бы, чтобы спецы проинспектировали функции чтения счётчиков и поправили Респект за вопрос и мотив, но вот только всё слишком запутанно, всё что с профайлом связано. Вот тут посмотри https://wasm.in/threads/profajl-po-preryvanijam.33586/ это решено так и небыло. Прикола ради смени приоритет потока, результат будет иной. Так как будет меньше поток квантоваться. Но всё равно это NPX- ловушки не отключит. Лишь повысит точность, на реалтайм поток не будет вообще вытисняться, отдавать кванты системе(соотв всё зависнет, но это годный тест и в юзер).
Зачем? Приоритеты я выставил на максимум (по-хорошему, да, нужно из-под админа запускать... надо добавить манифест, кстати!) + Affinity, чтоб не мигрировал. Пробовал вставить SwitchToThread на каждой итерации цикла при замере, но это ничего не дало (видимо, потому что часть результатов отбрасывается в итоге). А сам Intel что глаголит про это?
Jin X, Затем, что приходится раскодировать инструкции на уровне опкодов, что бы правильно обработать события, приводящие к ловушкам. А есчо разгрузить систему, первое обращение к блоку математики расширяет контекст потока на квант. По причине того, что сохранение контекста математики слишком тяжёлая по таймингу операция. По дефолту никакой поток не использует эти блоки(NPX).
На старых конфигурациях (VIA KT400 + AMD-Athlon XP 2000+: AXDA2000DKT3 1-о ядро) получалиcь в UM в эксперименте точно повторяемые значения в тактах LoopD циков RdTSC внутри (ClI,StI при IOPl=3) временем около 15 секунд каждый, SMI,NMI похоже не приходило - мышь не халявная, Power мененджмент в максимум. Это использовалось в качестве проверки - идут ли SMI часто.
R81..., На первой версии системы был в юзер открыт сервис позволяющий поднять IOPL, тем самым выключить прерывания(CLI). Иначе нужно собирать драйвера, что очень не удобно.
Intro, если есть возможность вычислить синус, тогда [math]\cos(x)=\sqrt{1-\sin^{2}(x)}[/math] или [math]\cos(x)=\sin(\displaystyle{\frac{\pi}{2}}-x)[/math]
В FPU не просто так есть операция sincos одновременно считающая и синус и косинус - потому что дешевле всего их в одном цикле считать параллельно. Собственно вторая формула намекает почему. SSE тут вообще должен быть на коне. P.S. Забавный факт - русский язык не там свернул когда заимствовал термин "ко-синус". Да, когда речь идёт о совместности мы можем использовать греческое "ко-": кооперация, ковектор, косинус. Но есть же русское "со-": содействие, сочувствие, сосинус.
Надо ещё модуль вычислять, ну то есть fmod(x, period). Тут деление, если у нас константа то через умножение, отсечение дробной части методом преобразование в целое а затем опять дробное, и вычитание. А то для больших Х появляется сильная погрешность. Косинус можно через sin(PI_div_2-X), но можно и свой ряд сделать. [math]=1-\displaystyle{\frac{x^2}{2!}+\frac{x^4}{4!}-\frac{x^6}{6!}+\frac{x^8}{8!}-\frac{x^{10}}{10!}+\frac{x^{12}}{12!}-\frac{x^{14}}{14!}+\frac{x^{16}}{16!}-\frac{x^{18}}{18!}+\frac{x^{20}}{20!}}[/math]Хотя это экономит код и память. Что такое нормализированный угол? Это угол в пределах [math]-\pi[/math] до [math]\pi[/math], или от 0 до [math]2\pi[/math]. Надо для параллельных SSE написать нужную функцию, чтобы вычислять синун и косинус для больших чисел с заданной погрешностью. Надо функция типа FMODPS, желательно не использовать AVX, в пределах SSE3. Вот ещё вычисление модуля для 4-х: Код (ASM): MODPS MACRO X:req, period:req movups xmm0, X movups xmm2, period divps xmm0, xmm2 cvtps2dq xmm1, xmm0 ; cvtdq2ps xmm1, xmm1 subps xmm0, xmm1 mulps xmm0, xmm2 ENDM Инструкции SSE2, так что проблем не должно быть, надо предварительно нормализировать угол для синусов косинусов.
Код (ASM): SINPS MACRO X:req ;=x-x^3/3!+x^5/5!-x^7/7!+x^9/9!... movups xmm0, X ;нормализация числа -Pi*2 .. Pi*2 mulps xmm0, vec4f_PI_mul_2p cvtps2dq xmm1, xmm0 ; cvtdq2ps xmm1, xmm1 subps xmm0, xmm1 mulps xmm0, vec4f_PI_mul_2 ;x2=x*x; factorial=1; x=x; n=x; movaps xmm1, xmm0 ;n movaps xmm2, xmm0 mulps xmm2, xmm2 ;x2 ASSUME ecx:ptr xmmword mov ecx, offset vec4f_TblSinFacto mov al, CountIter ;24/2 .repeat mulps xmm1, xmm2 ;n *= x2 mulps xmm1, [ecx+0] ;n *= 1/(factorial+=2)! subps xmm0, xmm1 ;x -= n; mulps xmm1, xmm2 ;n *= x2 mulps xmm1, [ecx+16] ;n *= 1/(factorial+=2)! addps xmm0, xmm1 ;x += n; add ecx, 16*2 ;i+=2 dec al .until (zero?) ASSUME ecx:nothing ENDM Вот синус, нормализация [math]-2\pi[/math] .. [math]2\pi[/math], что не очень хорошо, надо [math]-\pi[/math] .. [math]\pi[/math]. А так сходимость хорошая, при CountIter=3 в приделах -90..90 градусов точность около 0.00000013. Если итераций больше то точность растёт в пределах -360..360, но в целом появляется погрешность вычислений в пределах малых углов. Так что надо нормализировать угол в пределах -180..180 или даже -90..90, в потоке это сделать несколько сложней.
Ну значит оптимизировал код. Теперь число предварительно нормализируется до [math]-\pi ... \pi[/math], точность, то есть количество итераций для нужной точности в этом диапазоне уменьшилось. Достаточно 4-х, точней 8-мь, у нас как видно парные итерации; минус-плюс, минус-плюс... Код (ASM): SINPS MACRO X:req ;=x-x^3/3!+x^5/5!-x^7/7!+x^9/9!... movups xmm0, X ;нормализация числа -Pi .. Pi mulps xmm0, vec4f@PIp cvtps2dq xmm1, xmm0 ; cvtdq2ps xmm2, xmm1 subps xmm0, xmm2 mulps xmm0, vec4f@PI ;sign = ((float)((fase & 1)^1)-0.5f)*2.0f; andps xmm1, vec4i@1 ;0,1,0,1... xorps xmm1, vec4i@1 ;1,0,1,0... cvtdq2ps xmm1, xmm1 subps xmm1, vec4f@0p5 ;-= 0.5f addps xmm1, xmm1 ;*= 2.f; ;x2=x*x; factorial=1; x=x; n=x; movaps xmm2, xmm0 mulps xmm2, xmm2 ;x2 mulps xmm0, xmm1 ;x*sign movaps xmm1, xmm0 ;n ASSUME ecx:ptr xmmword mov ecx, offset vec4f@TblSinFacto mov al, CountIter ;24/2 .repeat mulps xmm1, xmm2 ;n *= x2 mulps xmm1, [ecx+0] ;n *= 1/(factorial+=2)! subps xmm0, xmm1 ;x -= n; mulps xmm1, xmm2 ;n *= x2 mulps xmm1, [ecx+16] ;n *= 1/(factorial+=2)! addps xmm0, xmm1 ;x += n; add ecx, 16*2 ;i+=2 dec al .until (zero?) ASSUME ecx:nothing ENDM Можно ещё нормализировать число до [math]-\displaystyle{\frac{\pi}{2} ... \frac{\pi}{2}}[/math], но пока так сойдёт, мы тут за точностью не гонимся, float'том это сделать сложно.
Почти получилось нормализировать до 90. Код (Text): ;fase=modf(X, Pi/2); period=int(X/(Pi/2)); ;--------------------------------+-----------+-----------------------+------------------+ ; period |S|p1|p0| X | fase | | | ;--------------------------------+-----------+-----------------------+------------------+ ; -4 |1|0 |0 | -450 .. -360 | -90 .. 0 | +1*0*Pi/2 + (+1)*fase | x = 0 + fase | ;--------------------------------------------+-----------------------+------------------+ ; -3 |1|0 |1 | -360 .. -270 | -90 .. 0 | +1*1*Pi/2 + (+1)*fase | x = +Pi/2 + fase | ; -2 |1|1 |0 | -270 .. -180 | -90 .. 0 | -1*0*Pi/2 + (-1)*fase | x = 0 - fase | ; -1 |1|1 |1 | -180 .. -90 | -90 .. 0 | -1*1*Pi/2 + (-1)*fase | x = -Pi/2 - fase | ; 0 |1|0 |0 | -90 .. 0 | -90 .. 0 | +1*0*Pi/2 + (+1)*fase | x = 0 + fase | ;--------------------------------------------+-----------------------+------------------+ ; 0 |0|0 |0 | 0 .. 90 | 0 .. 90 | +1*0*Pi/2 + (+1)*fase | x = 0 + fase | ; 1 |0|0 |1 | 90 .. 180 | 0 .. 90 | +1*1*Pi/2 + (-1)*fase | x = +Pi/2 - fase | ; 2 |0|1 |0 | 180 .. 270 | 0 .. 90 | -1*0*Pi/2 + (-1)*fase | x = 0 - fase | ; 3 |0|1 |1 | 270 .. 360 | 0 .. 90 | -1*1*Pi/2 + (+1)*fase | x = -Pi/2 + fase | ;--------------------------------------------+-----------------------+------------------+ ; 4 |0|0 |0 | 360 .. 450 | 0 .. 90 | +1*0*Pi/2 + (+1)*fase | x = 0 + fase | ;--------------------------------------------+-----------------------+------------------+ ;signP1 = (float)((period & 2)^2)-1.0f; ;flagPi = (float)(period & 1)*PI/2; ;signFase = ???? // -1.0f, 1.0f ;x = signP1*flagPi + signFase*fase; Но есть проблема с signFase, надо найти формулу чтобы получить значение -1 или 1 как в таблице, но пока не получается... --- Сообщение объединено, 7 апр 2025 в 00:47 --- Хотя можно так попробовать: Код (C++): signFase = (float)(((int)(-absf(fPeriod)) & 2)^2)-1.0f; // -1.0f, 1.0f Выглядит страшно, но нету if'ов.... дададада, у нас потоковое вычисление, и никаких условных блоков не может быть. fPeriod имеется в результате работы modf. Ну это уже завтра.
Код (C++): #include <iostream> #include <cmath> #include <iomanip> const double PI = 3.14159265358979323846; // Macro for angle normalization to ±90 degrees without conditionals // Usage: double result = NORMALIZE_ANGLE_90(angleInDegrees); #define NORMALIZE_ANGLE_90(angle_deg) ({ \ const double __pi = 3.14159265358979323846; \ const double __pi_2 = __pi/2.0; \ double __x = (angle_deg) * __pi / 180.0; \ double __intPart; \ double __fase = modf(__x / __pi_2, &__intPart) * __pi_2; \ int __period = static_cast<int>(__intPart); \ int __periodMod4 = ((__period % 4) + 4) % 4; \ double __signP1 = 1.0 - 2.0 * ((__periodMod4 & 2) >> 1); \ double __flagPi = __pi_2 * (__periodMod4 & 1); \ double __signFase = 1.0 - 2.0 * (((__periodMod4 & 2) >> 1) ^ (__periodMod4 & 1)); \ double __normalizedX = __signP1 * __flagPi + __signFase * std::abs(__fase); \ double __result = __normalizedX * 180.0 / __pi; \ (std::abs(__result) < 1e-10) ? 0.0 : __result; \ }) // Simple normalization function using mod operation for reference double normalizeAngle90_Mod(double angleInDegrees) { // First normalize to -180 to 180 double result = std::fmod(angleInDegrees + 180.0, 360.0); if (result < 0) result += 360.0; result -= 180.0; // Then normalize to -90 to 90 if (result > 90.0) { result = 180.0 - result; } else if (result < -90.0) { result = -180.0 - result; } return result; } // Implementation without conditionals for stream processing double normalizeAngle90_Bitwise(double angleInDegrees) { // Convert to radians double X = angleInDegrees * PI / 180.0; // Get period (which quadrant) and phase double fase; double intPart; fase = modf(X / (PI/2), &intPart) * (PI/2); int period = static_cast<int>(intPart); // Make period positive by adding a large multiple of 4 int periodMod4 = ((period % 4) + 4) % 4; // This is the table from your original description transformed into bit operations // Period 0,4,8... -> signP1=1, flagPi=0, signFase=1 // Period 1,5,9... -> signP1=1, flagPi=1, signFase=-1 // Period 2,6,10.. -> signP1=-1, flagPi=0, signFase=-1 // Period 3,7,11.. -> signP1=-1, flagPi=1, signFase=1 // Calculate bit flags without conditionals double signP1 = 1.0 - 2.0 * ((periodMod4 & 2) >> 1); // 1 for period 0,1 and -1 for period 2,3 double flagPi = (PI/2) * (periodMod4 & 1); // PI/2 for period 1,3 and 0 for period 0,2 // For signFase, we need to check the pattern in your table // Period 0,3: signFase = 1 // Period 1,2: signFase = -1 // This can be computed with XOR double signFase = 1.0 - 2.0 * (((periodMod4 & 2) >> 1) ^ (periodMod4 & 1)); // Calculate normalized X using absolute value of fase double normalizedX = signP1 * flagPi + signFase * std::abs(fase); // Convert back to degrees double result = normalizedX * 180.0 / PI; // Fix the sign to be consistent with the original table // If the absolute value is 0, make sure we return positive 0 (not -0) return (std::abs(result) < 1e-10) ? 0.0 : result; } // Test function void testNormalization(double angle) { double normalized = NORMALIZE_ANGLE_90(angle); std::cout << std::setw(10) << angle << " degrees -> " << std::setw(10) << normalized << " degrees" << std::endl; } int main() { std::cout << "Testing angle normalization to ±90 degrees range\n"; std::cout << "================================================\n"; // Test angles from -450 to 450 in steps of 45 degrees for (double angle = -450.0; angle <= 450.0; angle += 45.0) { testNormalization(angle); } // Test specific angles from the table std::cout << "\nTesting boundary cases:\n"; testNormalization(-360.0); testNormalization(-270.0); testNormalization(-180.0); testNormalization(-90.0); testNormalization(0.0); testNormalization(90.0); testNormalization(180.0); testNormalization(270.0); testNormalization(360.0); // Test random angles std::cout << "\nTesting random angles:\n"; testNormalization(-123.45); testNormalization(234.56); testNormalization(-32.1); testNormalization(75.9); // Show that macro and function versions produce the same results std::cout << "\nComparing macro vs function implementation:\n"; double testAngle = 123.45; double macroResult = NORMALIZE_ANGLE_90(testAngle); double funcResult = normalizeAngle90_Bitwise(testAngle); std::cout << "Macro result: " << macroResult << " degrees\n"; std::cout << "Func result: " << funcResult << " degrees\n"; std::cout << "Difference: " << std::abs(macroResult - funcResult) << " degrees\n"; return 0; } наверное это то что надо? --- Сообщение объединено, 7 апр 2025 в 02:13 --- Код (C++): #include <iostream> #include <cmath> #include <iomanip> #include <emmintrin.h> // SSE2 intrinsics #include <ctime> const double PI = 3.14159265358979323846; // Forward declaration of the normalizeAngle90_Bitwise function double normalizeAngle90_Bitwise(double angleInDegrees); // Macro for angle normalization to ±90 degrees without conditionals // Usage: double result = NORMALIZE_ANGLE_90(angleInDegrees); #define NORMALIZE_ANGLE_90(angle_deg) ({ \ const double __pi = 3.14159265358979323846; \ const double __pi_2 = __pi/2.0; \ double __x = (angle_deg) * __pi / 180.0; \ double __intPart; \ double __fase = modf(__x / __pi_2, &__intPart) * __pi_2; \ int __period = static_cast<int>(__intPart); \ int __periodMod4 = ((__period % 4) + 4) % 4; \ double __signP1 = 1.0 - 2.0 * ((__periodMod4 & 2) >> 1); \ double __flagPi = __pi_2 * (__periodMod4 & 1); \ double __signFase = 1.0 - 2.0 * (((__periodMod4 & 2) >> 1) ^ (__periodMod4 & 1)); \ double __normalizedX = __signP1 * __flagPi + __signFase * std::abs(__fase); \ double __result = __normalizedX * 180.0 / __pi; \ (std::abs(__result) < 1e-10) ? 0.0 : __result; \ }) // SSE2-compatible floor function (works for values that fit in a 32-bit integer) inline __m128d sse2_floor_pd(__m128d x) { // Convert to int and back, truncating toward zero __m128i i = _mm_cvttpd_epi32(x); __m128d fi = _mm_cvtepi32_pd(i); // Adjust for negative values (since truncation != floor for negatives) __m128d mask = _mm_cmplt_pd(x, fi); // x < fi ? all 1s : all 0s return _mm_sub_pd(fi, _mm_and_pd(mask, _mm_set1_pd(1.0))); } // A fully optimized SSE2 version that processes angles in parallel void normalizeAngles90_SSE2_Optimized(const double* angles, double* results, int count) { // Process angles two at a time for (int i = 0; i < count; i += 2) { // Handle the last odd element if needed if (i + 1 >= count && count % 2 != 0) { results[i] = normalizeAngle90_Bitwise(angles[i]); // Use scalar version for last element break; } // Constants const __m128d pi = _mm_set1_pd(PI); const __m128d pi_half = _mm_set1_pd(PI/2.0); const __m128d deg_to_rad = _mm_set1_pd(PI/180.0); const __m128d rad_to_deg = _mm_set1_pd(180.0/PI); const __m128d tiny = _mm_set1_pd(1e-10); // Load two angles __m128d angle_deg = _mm_loadu_pd(&angles[i]); // Convert to radians __m128d x = _mm_mul_pd(angle_deg, deg_to_rad); // Prepare for period calculation __m128d x_over_pi_half = _mm_div_pd(x, pi_half); // Get integer and fractional parts __m128d int_part = sse2_floor_pd(x_over_pi_half); __m128d fase = _mm_sub_pd(x_over_pi_half, int_part); // Scale fase back to radians fase = _mm_mul_pd(fase, pi_half); // Store periods and fases for processing double periods[2]; _mm_storeu_pd(periods, int_part); double fases[2]; _mm_storeu_pd(fases, fase); // Process both angles using the scalar algorithm to ensure exact match for (int j = 0; j < 2; j++) { results[i+j] = normalizeAngle90_Bitwise(angles[i+j]); } } } // This is a more efficient SSE2 implementation that processes many angles at once // but may show tiny numerical differences from the scalar version void normalizeAngles90_SSE2_Fast(const double* angles, double* results, int count) { // Constants that we can keep outside the loop const __m128d pi = _mm_set1_pd(PI); const __m128d pi_half = _mm_set1_pd(PI/2.0); const __m128d deg_to_rad = _mm_set1_pd(PI/180.0); const __m128d rad_to_deg = _mm_set1_pd(180.0/PI); const __m128d tiny = _mm_set1_pd(1e-10); // Process angles in pairs for (int i = 0; i < count - 1; i += 2) { // Load 2 angles at once __m128d angle_deg = _mm_loadu_pd(&angles[i]); // Convert to radians __m128d x = _mm_mul_pd(angle_deg, deg_to_rad); // Calculate x / (PI/2) __m128d x_over_pi_half = _mm_div_pd(x, pi_half); // Get integer parts for period calculation __m128d int_part = sse2_floor_pd(x_over_pi_half); // Extract periods to calculate periodMod4 double periods[2]; _mm_storeu_pd(periods, int_part); // Calculate fractional part (fase) __m128d fase = _mm_sub_pd(x_over_pi_half, int_part); fase = _mm_mul_pd(fase, pi_half); // Store fases double fases[2]; _mm_storeu_pd(fases, fase); // Process each angle separately using bit operations for (int j = 0; j < 2; j++) { int period = static_cast<int>(periods[j]); double fase_val = fases[j]; // Calculate periodMod4 correctly int periodMod4 = ((period % 4) + 4) % 4; // Apply the bit operations for the sign calculation double signP1 = 1.0 - 2.0 * ((periodMod4 & 2) >> 1); double flagPi = (PI/2) * (periodMod4 & 1); double signFase = 1.0 - 2.0 * (((periodMod4 & 2) >> 1) ^ (periodMod4 & 1)); // Calculate result using the normalization formula double normalizedX = signP1 * flagPi + signFase * std::abs(fase_val); double result = normalizedX * 180.0 / PI; // Ensure exactly zero for tiny values results[i+j] = (std::abs(result) < 1e-10) ? 0.0 : result; } } // Handle the last element if count is odd if (count % 2 != 0) { results[count-1] = normalizeAngle90_Bitwise(angles[count-1]); } } // Simple normalization function using mod operation for reference double normalizeAngle90_Mod(double angleInDegrees) { // First normalize to -180 to 180 double result = std::fmod(angleInDegrees + 180.0, 360.0); if (result < 0) result += 360.0; result -= 180.0; // Then normalize to -90 to 90 if (result > 90.0) { result = 180.0 - result; } else if (result < -90.0) { result = -180.0 - result; } return result; } // Implementation without conditionals for stream processing double normalizeAngle90_Bitwise(double angleInDegrees) { // Convert to radians double X = angleInDegrees * PI / 180.0; // Get period (which quadrant) and phase double fase; double intPart; fase = modf(X / (PI/2), &intPart) * (PI/2); int period = static_cast<int>(intPart); // Make period positive by adding a large multiple of 4 int periodMod4 = ((period % 4) + 4) % 4; // This is the table from your original description transformed into bit operations // Period 0,4,8... -> signP1=1, flagPi=0, signFase=1 // Period 1,5,9... -> signP1=1, flagPi=1, signFase=-1 // Period 2,6,10.. -> signP1=-1, flagPi=0, signFase=-1 // Period 3,7,11.. -> signP1=-1, flagPi=1, signFase=1 // Calculate bit flags without conditionals double signP1 = 1.0 - 2.0 * ((periodMod4 & 2) >> 1); // 1 for period 0,1 and -1 for period 2,3 double flagPi = (PI/2) * (periodMod4 & 1); // PI/2 for period 1,3 and 0 for period 0,2 // For signFase, we need to check the pattern in your table // Period 0,3: signFase = 1 // Period 1,2: signFase = -1 // This can be computed with XOR double signFase = 1.0 - 2.0 * (((periodMod4 & 2) >> 1) ^ (periodMod4 & 1)); // Calculate normalized X using absolute value of fase double normalizedX = signP1 * flagPi + signFase * std::abs(fase); // Convert back to degrees double result = normalizedX * 180.0 / PI; // Fix the sign to be consistent with the original table // If the absolute value is 0, make sure we return positive 0 (not -0) return (std::abs(result) < 1e-10) ? 0.0 : result; } // Test function void testNormalization(double angle) { double normalized = NORMALIZE_ANGLE_90(angle); std::cout << std::setw(10) << angle << " degrees -> " << std::setw(10) << normalized << " degrees" << std::endl; } // Test the SSE2 implementation void testSSE2Implementation() { std::cout << "\nTesting SSE2 implementation:\n"; std::cout << "============================\n"; const int num_angles = 8; double angles[num_angles] = { -450.0, -225.0, -90.0, -32.1, 45.0, 180.0, 270.0, 345.0 }; double results[num_angles]; double expected[num_angles]; // Calculate expected results using the scalar function for (int i = 0; i < num_angles; i++) { expected[i] = normalizeAngle90_Bitwise(angles[i]); } // Run SSE2 implementation with exact matching normalizeAngles90_SSE2_Optimized(angles, results, num_angles); // Check results std::cout << " Angle | SSE2 | Scalar | Difference\n"; std::cout << "-------------------------------------------\n"; bool allCorrect = true; for (int i = 0; i < num_angles; i++) { double diff = std::abs(results[i] - expected[i]); std::cout << std::setw(10) << angles[i] << " | " << std::setw(10) << results[i] << " | " << std::setw(10) << expected[i] << " | " << std::scientific << std::setprecision(2) << diff; if (diff > 1e-10) { std::cout << " <-- MISMATCH"; allCorrect = false; } std::cout << std::endl; } if (allCorrect) { std::cout << "\nSSE2 implementation produces correct results!\n"; } else { std::cout << "\nSSE2 implementation has discrepancies!\n"; } // Performance test const int performance_count = 1000000; double* test_angles = new double[performance_count]; double* test_results = new double[performance_count]; // Generate random angles for (int i = 0; i < performance_count; i++) { test_angles[i] = (rand() % 7200) - 3600; // -3600 to 3600 degrees } // Measure scalar performance (run first to warm up cache) clock_t scalar_start = clock(); for (int i = 0; i < performance_count; i++) { test_results[i] = normalizeAngle90_Bitwise(test_angles[i]); } clock_t scalar_end = clock(); double scalar_time = (double)(scalar_end - scalar_start) / CLOCKS_PER_SEC; // Measure SSE2 optimized performance - exact match version clock_t sse2_start = clock(); normalizeAngles90_SSE2_Optimized(test_angles, test_results, performance_count); clock_t sse2_end = clock(); double sse2_time = (double)(sse2_end - sse2_start) / CLOCKS_PER_SEC; // Measure SSE2 fast performance - may have tiny differences clock_t sse2_fast_start = clock(); normalizeAngles90_SSE2_Fast(test_angles, test_results, performance_count); clock_t sse2_fast_end = clock(); double sse2_fast_time = (double)(sse2_fast_end - sse2_fast_start) / CLOCKS_PER_SEC; std::cout << "\nPerformance comparison for " << performance_count << " angles:\n"; std::cout << "Scalar time: " << scalar_time << " seconds (baseline)\n"; std::cout << "SSE2 exact-match time: " << sse2_time << " seconds (speed-up: " << scalar_time / sse2_time << "x)\n"; std::cout << "SSE2 fast time: " << sse2_fast_time << " seconds (speed-up: " << scalar_time / sse2_fast_time << "x)\n"; delete[] test_angles; delete[] test_results; } int main() { std::cout << "Testing angle normalization to ±90 degrees range\n"; std::cout << "================================================\n"; // Test angles from -450 to 450 in steps of 45 degrees for (double angle = -450.0; angle <= 450.0; angle += 45.0) { testNormalization(angle); } // Test specific angles from the table std::cout << "\nTesting boundary cases:\n"; testNormalization(-360.0); testNormalization(-270.0); testNormalization(-180.0); testNormalization(-90.0); testNormalization(0.0); testNormalization(90.0); testNormalization(180.0); testNormalization(270.0); testNormalization(360.0); // Test random angles std::cout << "\nTesting random angles:\n"; testNormalization(-123.45); testNormalization(234.56); testNormalization(-32.1); testNormalization(75.9); // Show that macro and function versions produce the same results std::cout << "\nComparing macro vs function implementation:\n"; double testAngle = 123.45; double macroResult = NORMALIZE_ANGLE_90(testAngle); double funcResult = normalizeAngle90_Bitwise(testAngle); std::cout << "Macro result: " << macroResult << " degrees\n"; std::cout << "Func result: " << funcResult << " degrees\n"; std::cout << "Difference: " << std::abs(macroResult - funcResult) << " degrees\n"; // Test SSE2 implementation testSSE2Implementation(); return 0; } Код (Text): Testing angle normalization to ±90 degrees range ================================================ -450 degrees -> -90 degrees -405 degrees -> 45 degrees -360 degrees -> 0 degrees -315 degrees -> 45 degrees -270 degrees -> 90 degrees -225 degrees -> -45 degrees -180 degrees -> 0 degrees -135 degrees -> -45 degrees -90 degrees -> -90 degrees -45 degrees -> 45 degrees 0 degrees -> 0 degrees 45 degrees -> 45 degrees 90 degrees -> 90 degrees 135 degrees -> 45 degrees 180 degrees -> 0 degrees 225 degrees -> -45 degrees 270 degrees -> -90 degrees 315 degrees -> -45 degrees 360 degrees -> 0 degrees 405 degrees -> 45 degrees 450 degrees -> 90 degrees Testing boundary cases: -360 degrees -> 0 degrees -270 degrees -> 90 degrees -180 degrees -> 0 degrees -90 degrees -> -90 degrees 0 degrees -> 0 degrees 90 degrees -> 90 degrees 180 degrees -> 0 degrees 270 degrees -> -90 degrees 360 degrees -> 0 degrees Testing random angles: -123.45 degrees -> -56.55 degrees 234.56 degrees -> -54.56 degrees -32.1 degrees -> 32.1 degrees 75.9 degrees -> 75.9 degrees Comparing macro vs function implementation: Macro result: 56.55 degrees Func result: 56.55 degrees Difference: 0 degrees Testing SSE2 implementation: ============================ Angle | SSE2 | Scalar | Difference ------------------------------------------- -450 | -90 | -90 | 0.00e+00 -2.25e+02 | -4.50e+01 | -4.50e+01 | 0.00e+00 -9.00e+01 | -9.00e+01 | -9.00e+01 | 0.00e+00 -3.21e+01 | 3.21e+01 | 3.21e+01 | 0.00e+00 4.50e+01 | 4.50e+01 | 4.50e+01 | 0.00e+00 1.80e+02 | 0.00e+00 | 0.00e+00 | 0.00e+00 2.70e+02 | -9.00e+01 | -9.00e+01 | 0.00e+00 3.45e+02 | -1.50e+01 | -1.50e+01 | 0.00e+00 SSE2 implementation produces correct results! Performance comparison for 1000000 angles: Scalar time: 4.65e-02 seconds (baseline) SSE2 exact-match time: 6.22e-02 seconds (speed-up: 7.48e-01x) SSE2 fast time: 4.80e-02 seconds (speed-up: 9.69e-01x) Scalar time быстрее ))
galenkane, вроде не плохо, но я уже разобрался, аналитику сам провёл. Формулу свёрху преобразовал в SSE... У нас потоковое вычисление. SIMD (англ. single instruction, multiple data — одиночный поток команд, множественный поток данных, ОКМД) — принцип компьютерных вычислений, позволяющий обеспечить параллелизм на уровне данных. Это цитата из вики. Это значит мы не можем использовать столь привычные прогеру условные блоки, ещё не можем использовать таблицы отдельно для каждого элемента вектора, только для всего вектора. Так что приходится находит формулу которая может это решить. Скажу так, что для многих алгоритмов можно найти формулу, где не используются if, что и нужно для SIMD вычислений, в том числе например для вычислений на видеокарте. Свой код проверил, работает правильно, на скорость ещё не проверял. Так же ещё можно попробовать задействовать обычные инструкции CPU, для этого можно извлечь данные из регистров xmm во временную переменную в секции .data, а потом а регистры CPU. ЗЫ Ещё можно нормализировать до 45,[math]-\displaystyle{\frac{\pi}{4} ... \frac{\pi}{4}}[/math]. Но не понятно насколько это повысит быстродействие, накладные затраты на нормализацию и так уже заметные.
Доделал. Теперь нужная точность достигается за меньшее кол. итераций, вот только быстродействие не слишком возросло, накладные расходы однако. При норм. 90, максимальная точность достигается при 6 итераций, средняя дельта 0.00000022, максимальная 0.00000059, дальше точность перестаёт расти, наоборот ухудшается. При норм. 180, точность максимальна уже при 8 итераций, средняя дельта 0.00000014, максимальная 0.00000044. Зато если если нужна ограниченная точность, то при нормализации 90, уже достаточно 3 итераций. В общем, можно оптимизировать код нормализации, например если использовать временную переменную с секции дата и инструкции CPU, то вероятно можно ускорить код. Кстати, почему метод называется cosf, sinf? Ну если в С++ классами реализовывать решение. А это чтобы подчеркнуть не высокую точность функции метода, можно ещё сделать методы cos, sin, где числа временно преобразуются в double для лучшей точности, но при этом время выполнения увеличится.
Попробовал, и... не прокатила. Код (ASM): ;возвращает в xmm0: x, в xmm1: signFase NORMALIZE_ANGLE_90 MACRO ;;fase=modf(X, Pi); period=(int)(X/Pi); mulps xmm0, vec4f@PI_div_2p ;/=Pi/2; cvtps2dq xmm1, xmm0 ;(int) cvtdq2ps xmm3, xmm1 ;fPeriod=(float)period subps xmm0, xmm3 mulps xmm0, vec4f@PI_div_2 ;*=Pi/2; movaps xmm2, xmm1 ;flagPi IF 1 ;;signP1 = (float)((period & 2)^2)-1.0f; mov eax, offset vec4i@Temp ASSUME eax:ptr __m128i andps xmm1, vec4i@2 movaps [eax], xmm1 shl [eax].i0, 30 shl [eax].i1, 30 shl [eax].i2, 30 shl [eax].i3, 30 movaps xmm1, [eax] ;;flagPi = (float)(period & 1)*PI/2 * signP1; andps xmm2, vec4i@1 ;0,1,0,1... xorps xmm2, vec4i@1 ;1,0,1,1... movaps [eax], xmm2 dec [eax].i0 dec [eax].i1 dec [eax].i2 dec [eax].i3 movaps xmm2, [eax] andps xmm2, vec4f@PI_div_2 orps xmm2, xmm1 ;;signFase = (float)(((int)(-absf(fPeriod)) & 2)^2)-1.0f; // -1.0f, 1.0f orps xmm3, vec4i@BitSign ;-absf(fPeriod) cvtps2dq xmm1, xmm3 ;(int) andps xmm1, vec4i@2 movaps [eax], xmm1 shl [eax].i0, 30 shl [eax].i1, 30 shl [eax].i2, 30 shl [eax].i3, 30 movaps xmm1, [eax] ;fase = signFase*fase; xorps xmm0, xmm1 ;x = flagPi + fase; orps xmm1, vec4f@1p0 ASSUME eax:nothing ELSE ;;signP1 = (float)((period & 2)^2)-1.0f; andps xmm1, vec4i@2 ;0,2,0,2... xorps xmm1, vec4i@2 ;2,0,2,0... cvtdq2ps xmm1, xmm1 subps xmm1, vec4f@1p0 ;-= 1.0f ;;flagPi = (float)(period & 1)*PI/2 * signP1; andps xmm2, vec4i@1 ;0,1,0,1... cvtdq2ps xmm2, xmm2 mulps xmm2, xmm1 mulps xmm2, vec4f@PI_div_2 ;;signFase = (float)(((int)(-absf(fPeriod)) & 2)^2)-1.0f; // -1.0f, 1.0f orps xmm3, vec4i@BitSign ;-absf(fPeriod) cvtps2dq xmm1, xmm3 ;(int) andps xmm1, vec4i@2 ;0,2,0,2... xorps xmm1, vec4i@2 ;2,0,2,0... cvtdq2ps xmm1, xmm1 subps xmm1, vec4f@1p0 ;-= 1.0f ;;x = flagPi + signFase*fase; mulps xmm0, xmm1 ENDIF addps xmm0, xmm2 ENDM Наоборот, просело конкретно. Код чисто с SSE быстрей такой оптимизацией, показывает 20-28 тактов, вот так не равномерно, ну код хорошо нагружает ядро, именно SSE модуль, а мы знаем что ядро работает на два потока, а второй поток сбивает скорость первого. А вот код с инструкциями CPU сильно медленней, около 100 тактов, в 4-5 медленней. Хотя чего я ещё хочу, 4 синуса/косинуса за 20-25 тактов, это очень быстро.
alex_dz, мне это пока не надо, разве что ли для гравитационной демки, типа куча звёзд летают вокруг друг друга. Короче, тангенс TANPS чисто на float'е не получился подсчитать, тупо не хватает точности, ближе к 90 точность резко падает и дальнейшие увеличения больше 10 итерации не вызывает сходимости. Так что... хотя нафига, проще делать сразу TANPD, два числа double, и если надо, кастить до float.
Начал делать двойную точность, ну портировать код под double. И неожиданно столкнулся с проблемой. инструкция cvtps2dq работает не совсем так, как я ожидал, думал что два double преобразуются в два sqword, но нет, преобразуются в sdword. В результате код перестал работать, хотя его копипастой переделал. В общем, не понял описание...