Не совсем про ХЛ, но все же может быть полезно и моддерам.
В общем, понадобилось мне точно замерять, насколько та или иная реализация алгоритма быстрее. Есть конечно много вариантов, можно 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;
6
7
void Prof_Init( void )
8
{
9
__g_ProfilerStart = 0;
10
__g_ProfilerEnd = 0;
11
__g_ProfilerEnd2 = 0;
12
__g_ProfilerSpare = 0;
13
14
__asm {
15
//Check TSC support (bit 4 in edx, with eax=1)
16
mov eax, 1
17
cpuid
18
test edx, 10h
19
jz unsupported
20
21
//Check TSC value, it must be non-zero
22
rdtsc
23
test eax, eax
24
jnz supported
25
test edx, edx
26
jz unsupported
27
}
28
supported:
29
__g_ProfilerSupported = 1;
30
return;
31
32
unsupported:
33
__g_ProfilerSupported = 0;
34
return;
35
}
Сами функции профилирования - это макросы, т.к. они должны вставляться строго в код (тратить такты на вызов функций недопустимо). Обратите внимание - во втором макросе RDTSC вызывается дважды. Это делается, чтобы скорректировать полученное значение на такты, затраченные на сами команды профилирования.
C++ Source Code:
1
#define Prof_Start() \
2
{ \
3
if (__g_ProfilerSupported) \
4
{ \
5
__asm pushad \
6
__asm rdtsc \
7
__asm mov DWORD PTR[__g_ProfilerStart+4], edx \
8
__asm mov DWORD PTR[__g_ProfilerStart], eax \
9
__asm popad \
10
} \
11
}
12
13
#define Prof_End() \
14
{ \
15
if (__g_ProfilerSupported) \
16
{ \
17
__asm pushad \
18
__asm rdtsc \
19
__asm mov DWORD PTR[__g_ProfilerEnd+4], edx \
20
__asm mov DWORD PTR[__g_ProfilerEnd], eax \
21
__asm popad \
22
__asm pushad \
23
__asm rdtsc \
24
__asm mov DWORD PTR[__g_ProfilerEnd2+4], edx \
25
__asm mov DWORD PTR[__g_ProfilerEnd2], eax \
26
__asm popad \
27
} \
28
}
Далее собственно обработка результатов. Первая функция вычисляет запоминает значение первого профилирования, вторая - вычисляет второе значение и выводит, какой код быстрее и на сколько процентов.
unsigned__int64 total = __g_ProfilerEnd-__g_ProfilerStart-(__g_ProfilerEnd2-__g_ProfilerEnd);
12
double ratio;
13
14
if (total >= __g_ProfilerSpare) {
15
ratio = (double)(total-__g_ProfilerSpare)/total;
16
printf("First code is %.2f%% faster\n", ratio*100);
17
} else {
18
ratio = (double)(__g_ProfilerSpare-total)/__g_ProfilerSpare;
19
printf("Second code is %.2f%% faster\n", ratio*100);
20
}
21
} else {
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");
26
}
27
}
Эта функция просто выводит результат профилирования:
C++ Source Code:
1
void Prof_Results( void )
2
{
3
if (__g_ProfilerSupported) {
4
unsigned__int64 total = __g_ProfilerEnd-__g_ProfilerStart-(__g_ProfilerEnd2-__g_ProfilerEnd);
5
printf("--- Profile results: %I64d\n", total);
6
} else {
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");
11
}
12
}
Вот и все. Профилировать можно так:
C++ Source Code:
1
Prof_Init();
2
Prof_Start();
3
4
//Code 1
5
6
Prof_End();
7
Prof_Store();
8
9
Prof_Start();
10
11
//Code 2
12
13
Prof_End();
14
Prof_RatioResults();
Учтите, что при профилировании длительно выполняемого кода неизбежны ошибки. Это следствие вытесняющей многозадачности ОС: она может прервать по собственному желанию выполнение вашего кода и переключить процессор на код ОС. Поэтому во время профилирования не сворачивайте окна, не двигайте мышь и т.п.
Ваше мнение о профайлере? Предложения и замечания приветствуются