HLFX.Ru Forum (https://hlfx.ru/forum/index.php)
- Half-Life SDK (https://hlfx.ru/forum/forumdisplay.php?forumid=8)
-- Замеряем скорость выполнения кода (https://hlfx.ru/forum/showthread.php?threadid=595)
Отправлено XaeroX 02-04-2007 в 17:55:
 Замеряем скорость выполнения кода
Не совсем про ХЛ, но все же может быть полезно и моддерам. 
В общем, понадобилось мне точно замерять, насколько та или иная реализация алгоритма быстрее. Есть конечно много вариантов, можно GetTickCount использовать или QueryPerformanceCounter, но тут есть две проблемы:
- Невысокая точность
- Сами эти функции содержат код
Это не так принципиально для профилирования больших функций, а если вам надо профилировать небольшие ассемблерные вставки?
Для этого я выбрал TSC - timestamp counter. Это модельно-зависимый регистр процессора, содержимое которого увеличивается с каждым тактом ядра и сбрасывается при остановке (сигналом RESET). Регистр этот 64-битный, так что и работать придется с 64-битными переменными (в MS Visual Studio это тип __int64). Есть тут две тонкости. Во-первых, нужно проверить, так как регистр модельно-зависимый, то по определению он присутствует не на всех моделях процессоров, так что нам нужно будет проверить его наличие. И второе - чтение из него выполняется командой RDTSC, которая может быть сделана защищенной (доступной только в т.н. "нулевом кольце" привилегий). Под WinXP вы такие привилегии не получите... Но обычно она все же не защищена, так что эту проверку я опускаю. Ну и на всякий случай проверим, что в регистре ненулевое значение (иначе - что-то пошло не так).
Я написал три функции - первые две служат для сравнения производительности двух участков кода, а вторая просто выводит число тактов за профилирование.
C++ Source Code:
1  | unsigned __int64 __g_ProfilerStart;  | 
2  | unsigned __int64 __g_ProfilerEnd;  | 
3  | unsigned __int64 __g_ProfilerEnd2;  | 
4  | unsigned __int64 __g_ProfilerSpare;  | 
5  | unsigned __int8  __g_ProfilerSupported;  | 
12  |   __g_ProfilerSpare = 0;  | 
15  |     //Check TSC support (bit 4 in edx, with eax=1)  | 
21  |     //Check TSC value, it must be non-zero  | 
29  |   __g_ProfilerSupported = 1;  | 
33  |   __g_ProfilerSupported = 0;  | 
 
Сами функции профилирования - это макросы, т.к. они должны вставляться строго в код (тратить такты на вызов функций недопустимо). Обратите внимание - во втором макросе RDTSC вызывается дважды. Это делается, чтобы скорректировать полученное значение на такты, затраченные на сами команды профилирования.
C++ Source Code:
3  | if (__g_ProfilerSupported) \  | 
7  | __asm mov DWORD PTR[__g_ProfilerStart+4], edx \  | 
8  | __asm mov DWORD PTR[__g_ProfilerStart], eax \  | 
15  | if (__g_ProfilerSupported) \  | 
19  | __asm mov DWORD PTR[__g_ProfilerEnd+4], edx \  | 
20  | __asm mov DWORD PTR[__g_ProfilerEnd], eax \  | 
24  | __asm mov DWORD PTR[__g_ProfilerEnd2+4], edx \  | 
25  | __asm mov DWORD PTR[__g_ProfilerEnd2], eax \  | 
 
Далее собственно обработка результатов. Первая функция вычисляет запоминает значение первого профилирования, вторая - вычисляет второе значение и выводит, какой код быстрее и на сколько процентов.
C++ Source Code:
3  |   if (__g_ProfilerSupported) { | 
4  |     __g_ProfilerSpare = __g_ProfilerEnd-__g_ProfilerStart-(__g_ProfilerEnd2-__g_ProfilerEnd);  | 
8  | void Prof_RatioResults( void )  | 
10  |   if (__g_ProfilerSupported) { | 
11  |     unsigned __int64 total = __g_ProfilerEnd-__g_ProfilerStart-(__g_ProfilerEnd2-__g_ProfilerEnd);  | 
14  |     if (total >= __g_ProfilerSpare) { | 
15  |       ratio = (double)(total-__g_ProfilerSpare)/total;  | 
16  |       printf("First code is %.2f%% faster\n", ratio*100); | 
18  |       ratio = (double)(__g_ProfilerSpare-total)/__g_ProfilerSpare;  | 
19  |       printf("Second code is %.2f%% faster\n", ratio*100); | 
22  |     printf("--- Profiler not supported ---\n"); | 
23  |     printf("Possible reasons:\n"); | 
24  |     printf("\t- Your CPU does not support timestamp counter\n"); | 
25  |     printf("\t- RDTSC is a priveleged instruction (only at CPL0)\n"); | 
 
Эта функция просто выводит результат профилирования:
C++ Source Code:
1  | void Prof_Results( void )  | 
3  |   if (__g_ProfilerSupported) { | 
4  |     unsigned __int64 total = __g_ProfilerEnd-__g_ProfilerStart-(__g_ProfilerEnd2-__g_ProfilerEnd);  | 
5  |     printf("--- Profile results: %I64d\n", total); | 
7  |     printf("--- Profiler not supported ---\n"); | 
8  |     printf("Possible reasons:\n"); | 
9  |     printf("\t- Your CPU does not support timestamp counter\n"); | 
10  |     printf("\t- RDTSC is a priveleged instruction (only at CPL0)\n"); | 
 
Вот и все. Профилировать можно так:
C++ Source Code:
Учтите, что при профилировании длительно выполняемого кода неизбежны ошибки. Это следствие вытесняющей многозадачности ОС: она может прервать по собственному желанию выполнение вашего кода и переключить процессор на код ОС. Поэтому во время профилирования не сворачивайте окна, не двигайте мышь и т.п. 
Ваше мнение о профайлере? Предложения и замечания приветствуются 
__________________
Отправлено Government-Man 03-04-2007 в 07:50:
 
По поводу самого кода ничего сказать не могу - ибо в асме я разбираюсь мягко говоря плоховато. 
 А за тутор спасибо - сам искал. 
Отправлено Дядя Миша 03-04-2007 в 12:21:
 
Government-Man не переживай, ксерокс этот профайлер тоже не сразу написал, а после соответствующей подготовки.
Отправлено Government-Man 03-04-2007 в 14:32:
 
Цитата:
Дядя Миша писал:
после соответствующей подготовки
См. тему "Что вы нюхаете"