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:
Цитата:
Дядя Миша писал:
после соответствующей подготовки
См. тему "Что вы нюхаете"