PDA

Просмотр полной версии : [Урок] Сравнение производительности кода на Pawn (профилирование)



Daniel_Cortez
16.08.2015, 18:20
Привет всем участникам Pro-Pawn.
В данном уроке я объясню вам, как сравнивать производительность разных вариантов кода. Сделано это будет с помощью написанного мной профайлера.

Примеры вопросов, на которые можно найти ответ с помощью профилирования:
Работает ли функция strcat быстрее, чем format, и если да, то в каких случаях?
Как лучше проверять подключение игрока: с помощью IsPlayerConnected или добавлением в итератор (foreach.inc) и использованием функции Itter_Contains ?



Код профайлера:


// Profiler v1.3 (copyright (c) 2014-2017 Daniel_Cortez) \\ Pro-Pawn.ru
// Условия использования данного кода: http://pro-pawn.ru/showthread.php?12585


/*======== Настройки =========================================================*/
// Кол-во итераций в циклах.
const PROFILER_ITERATIONS_MAJOR = 10_000;
const PROFILER_ITERATIONS_MINOR = 1_000;

// Названия отрывков кода.
new const code_snippets_names[2][] =
{
{"отрывок 1"},
{"отрывок 2"}
};

// Здесь вы можете объявить переменные, используемые в профилируемых отрывках кода
// и выполнить некоторые действия непосредственно перед профилированием.
#define Prerequisites();\
// <ваш код>

/*
Собственно, сами отрывки кода, которые нужно тестировать.
Если код состоит из нескольких строк, переносите их обратным слэшем.
Пример:
#define CodeSnippet1();\
DoSomething();\
DoSomethingElse();
*/
#define CodeSnippet0();\
// <ваш код>

#define CodeSnippet1();\
// <ваш код>
/*======== Конец настроек ===================================================*/



// Не рекомендую изменять следующий код, если вы в нём не разбираетесь.
#tryinclude <a_samp>
#if defined _samp_included
#define LINE_BREAK ""
#else
#define LINE_BREAK "\n"
#include <core>
#include <time>
#define GetTickCount() tickcount()
#endif

#define _PROFILE_START(%0)\
__t[%0] = GetTickCount();\
for (__j = 0; __j < PROFILER_ITERATIONS_MINOR; ++__j)\
{
#define _PROFILE_END(%0) __t[%0] = GetTickCount()-__t[%0]

#if defined _samp_included
#define PROFILE_START(%0); _PROFILE_START(%0)
#define PROFILE_END(%0);\
}\
_PROFILE_END(%0);
#else
#define PROFILE_START(%0); _PROFILE_START(%0)
#define PROFILE_END(%0);\
}\
if ((_PROFILE_END(%0)) < 0) {--__i; continue;}
#endif

#define CODE_SNIPPET_EXISTS(%0)\
(sizeof(code_snippets_names) >= ((%0) + 1)) && (defined CodeSnippet%0)


new code_snippets_time = {0, ...};

main()
{
new __i, __j, __t[sizeof(code_snippets_names)];
#emit zero.pri
#emit lctrl 7
#emit stor.s.pri __i
#if CODE_SNIPPET_EXISTS(2)
static const ending[] =
#if (sizeof(code_snippets_names) <= 4)
"ка";
#else
"ков";
#endif
printf(
"Тестирование: %d отрыв%s кода." LINE_BREAK,
sizeof(code_snippets_names), ending
);
#else
printf(
"Тестирование: <%s> vs <%s>" LINE_BREAK,
code_snippets_names[0], code_snippets_names[1]
);
#endif
static const JIT_status_strings[2][] =
{"интерпретируемый", "с JIT-компиляцией"};
printf(
"Режим: %s, %dx%d итераций.\a" LINE_BREAK,
JIT_status_strings[__i],
PROFILER_ITERATIONS_MAJOR, PROFILER_ITERATIONS_MINOR
);
Prerequisites();
for (__i = 0; __i < PROFILER_ITERATIONS_MAJOR; ++__i)
{
PROFILE_START(0);
CodeSnippet0();
PROFILE_END(0);
PROFILE_START(1);
CodeSnippet1();
PROFILE_END(1);
#if CODE_SNIPPET_EXISTS(2)
PROFILE_START(2);
CodeSnippet2();
PROFILE_END(2);
#endif
#if CODE_SNIPPET_EXISTS(3)
PROFILE_START(3);
CodeSnippet3();
PROFILE_END(3);
#endif
#if CODE_SNIPPET_EXISTS(4)
PROFILE_START(4);
CodeSnippet4();
PROFILE_END(4);
#endif
#if CODE_SNIPPET_EXISTS(5)
PROFILE_START(5);
CodeSnippet5();
PROFILE_END(5);
#endif
#if CODE_SNIPPET_EXISTS(6)
PROFILE_START(6);
CodeSnippet6();
PROFILE_END(6);
#endif
#if CODE_SNIPPET_EXISTS(7)
PROFILE_START(7);
CodeSnippet7();
PROFILE_END(7);
#endif
#if CODE_SNIPPET_EXISTS(8)
PROFILE_START(8);
CodeSnippet8();
PROFILE_END(8);
#endif
#if CODE_SNIPPET_EXISTS(9)
PROFILE_START(9);
CodeSnippet9();
PROFILE_END(9);
#endif
#if CODE_SNIPPET_EXISTS(10)
PROFILE_START(10);
CodeSnippet10();
PROFILE_END(10);
#endif
#if CODE_SNIPPET_EXISTS(11)
PROFILE_START(11);
CodeSnippet11();
PROFILE_END(11);
#endif
#if CODE_SNIPPET_EXISTS(12)
PROFILE_START(12);
CodeSnippet12();
PROFILE_END(12);
#endif
#if CODE_SNIPPET_EXISTS(13)
PROFILE_START(13);
CodeSnippet13();
PROFILE_END(13);
#endif
#if CODE_SNIPPET_EXISTS(14)
PROFILE_START(14);
CodeSnippet14();
PROFILE_END(14);
#endif
#if CODE_SNIPPET_EXISTS(15)
PROFILE_START(15);
CodeSnippet15();
PROFILE_END(15);
#endif
for (__j = 0; __j < sizeof(code_snippets_names); ++__j)
code_snippets_time[__j] += __t[__j];
}
for (__i = 0; __i < sizeof(code_snippets_names); ++__i)
printf(
"%s: %d" LINE_BREAK,
code_snippets_names[__i], code_snippets_time[__i]
);
printf("\a\a" LINE_BREAK);
}



Условия использования:


Вам предоставляется право на копирование данного ПО, изменение и распространение и другие виды использования при соблюдении условий данной лицензии.
Данное ПО предоставляется "как есть", без каких-либо гарантий (как явно выраженных, так и подразумеваемых).
Автор ПО не несёт никакой ответственности за ущерб, причинённый в результате использования данного ПО и/или каких-либо других действий с ним.
В данном ПО и во всех производных от него работах заявление об авторстве и ссылка на страницу с данной лицензией должны оставаться в неизменном виде (т.е. как в оригинале). Также, если это производная работа, она должна быть явным образом помечена как изменённая версия и не выдаваться за оригинал.
Вы можете копировать, распространять и публиковать настройки профайлера отдельно - в этом случае указание авторства и ссылки на данную страницу не требуется.


А теперь более простым языком*:
Не выдавайте данный код за свой.
Строки с упоминанием об авторстве и ссылкой на эту страницу должны оставаться в коде, в неизменном виде.
Вы можете изменять код, если соблюдаются остальные условия использования (например, можете удалить все комментарии, кроме копирайта и ссылки на эту страницу).
Если вы публикуете изменённую версию профайлера, обязательно указывайте, что это изменённая версия, а не оригинал.
Настройки профайлера не попадают под эти условия, можете делать с ними всё, что захотите.

[size=1]*Приведённое выше упрощённое содержание не является полной лицензией.



Настройка и использование:

Для начала разберём назначение каждой настраиваемой константы:
Количество итераций цикла профилирования.
PROFILER_ITERATIONS_MINOR - кол-во итераций перед измерением времени выполнения.
PROFILER_ITERATIONS_MAJOR - кол-во измерений времени.
Весь процесс профилирования сделан с помощью вложенных циклов. Таким образом уменьшаются погрешности от многократных вызовов GetTickCount.
Также сглаживаются погрешности из-за разной нагрузки на ЦП в разные моменты времени. Например, во время профилирования может включиться запланированная по расписанию проверка антивирусом - это намного меньше повлияет на результаты профилирования в случае с вложенными циклами.
Названия отрывков кода.
Для этой цели предназначен массив строк code_snippets_names.
Отрывки кода.
Для указания сравниваемых отрывков кода предназначены макросы "CodeSnippet0();", "CodeSnippet1();", ... "CodeSnippet15();".
Также для подготовительных действий (объявление и инициализация переменных, etc.) есть макрос Prerequisites.

Рассмотрим использование профайлера на примере измерения производительности функций format и strcat для конкатенации строк:


/*======== Настройки =========================================================*/
const PROFILER_ITERATIONS_MAJOR = 10_000;
const PROFILER_ITERATIONS_MINOR = 1_000;

new const code_snippets_names[2][] =
{
{"format"},
{"strcat"}
};

#define Prerequisites();\
static const str1[] = "1234567890123456789012345678901234567890";\
static const str2[] = "1234567890123456789012345678901234567890";\
new buffer[128];

#define CodeSnippet0();\
format(buffer, sizeof(buffer), "%s%s", str1, str2);

#define CodeSnippet1();\
buffer = str1, strcat(buffer, str2);
/*======== Конец настроек ====================================================*/




Автор статьи: Daniel_Cortez (http://pro-pawn.ru/member.php?100-Daniel_Cortez)

Специально для Pro-Pawn.ru (http://www.pro-pawn.ru)
Копирование данной статьи на других ресурсах без разрешения автора запрещено!

Desulaid
16.08.2015, 18:39
Все равно скопипастят :victory:

Daniel_Cortez
07.11.2015, 11:43
Обновил код профайлера, теперь он должен работать не только на сервере SA:MP, но и под управлением оригинального интерпретатора Pawn.
В алгоритм измерения скорости пришлось добавить обход проблемы, при которой функция tickcount почему-то возвращает неправильные значения.

Daniel_Cortez
29.12.2015, 19:26
Ещё одно обновление. Теперь можно профилировать от 2 до 16 отрывков кода, не внося изменений в код самого профайлера.

Daniel_Cortez
03.05.2017, 18:34
Добавил к переменным в коде профилирования префикс "__". Теперь в макросах Prerequisites и CodeSnippet* можно объявлять и использовать переменные с именами "i", "j" и "t" без конфликта имён.

DCPSHER
01.06.2018, 22:29
Есть пара вопросов:
Правильно ли я понимаю, что профайлер нужно использовать как отдельный filterscript или gamemode? Т.к. в нем есть main() и его нельзя использовать как простой include, если в моде уже есть main()
Как можно запустить профилирование отложенно? Например как указано в статье "Как лучше проверять подключение игрока: с помощью IsPlayerConnected или добавлением в итератор (foreach.inc) и использованием функции Itter_Contains ?". Конкретно я хочу потестировать функции вычисления расстояний между игроками и т.п., но мне нужно их включать, когда игроки уже на сервере, а не при его старте.

Daniel_Cortez
03.06.2018, 19:52
Есть пара вопросов:
Правильно ли я понимаю, что профайлер нужно использовать как отдельный filterscript или gamemode? Т.к. в нем есть main() и его нельзя использовать как простой include, если в моде уже есть main()
Как можно запустить профилирование отложенно? Например как указано в статье "Как лучше проверять подключение игрока: с помощью IsPlayerConnected или добавлением в итератор (foreach.inc) и использованием функции Itter_Contains ?". Конкретно я хочу потестировать функции вычисления расстояний между игроками и т.п., но мне нужно их включать, когда игроки уже на сервере, а не при его старте.
Вы так каждый день будете пытаться "поднять" тему новыми одинаковыми постами? (удалил пост за сегодняшний день) Сообщение ваше видел ещё вчера, просто не было времени ответить.

Отложенное профилирование в моём коде не предусмотрено. Я могу реализовать его и переоформить всю эту работу в инклуд, но сейчас всё свободное время уходит на подготовку статей для Pro-Pawn Wiki. Пока что внёс это в список задач на будущее.

DCPSHER
03.06.2018, 20:54
Вы так каждый день будете пытаться "поднять" тему новыми одинаковыми постами? (удалил пост за сегодняшний день) Сообщение ваше видел ещё вчера, просто не было времени ответить.

Отложенное профилирование в моём коде не предусмотрено. Я могу реализовать его и переоформить всю эту работу в инклуд, но сейчас всё свободное время уходит на подготовку статей для Pro-Pawn Wiki. Пока что внёс это в список задач на будущее.

Прошу прощения за поднятие, вчера отвечал без реплая, поэтому подумал, что уведомление могло до вас не дойти, а так как тема уже немолодая, вы ее не проверяете.
Буду очень ждать данный инклуд!
Большое спасибо за ответ!