PDA

Просмотр полной версии : [Урок] Мифы о Pawn-скриптинге - #5



Daniel_Cortez
13.01.2016, 17:58
Внимание: данная тема закрыта для защиты от копирования.
Если есть какие-то вопросы, замечания или просто пожелания по поводу данного урока - оставляйте их здесь (http://pro-pawn.ru/showthread.php?12774-%D0%9E%D0%B1%D1%81%D1%83%D0%B6%D0%B4%D0%B5%D0%BD%D0%B8%D0%B5).




Миф 5: "Функция sizeof медленная, а ещё с ней код медленно компилируется, не надо пользоваться ей."

Статус: Опровергнут.

Описание:

Описание #не_нужно, всё уже есть в названии мифа. Равно, как и не нужно говорить, с какого форума была взята та цитата.
Впрочем, популярен этот миф не только на г-и: http://ihost.pro-pawn.ru/image.php?di=HU2W
И я бы не стал опровергать столь очевидный миф, если бы не "профессионалы", которые время от времени ещё и набегают сюда.


Доказательство:
На самом деле в названии мифа сразу две ошибки.
Во-первых, sizeof - это не функция, а оператор, с помощью которого можно заставить компилятор автоматически подсчитать размер массива в ячейках.
Во-вторых, размер массива постоянен и результат работы оператора sizeof вычисляется компилятором. Код не может выполняться медленнее из-за константы.

Рассмотрим это на следующем примере:

#define fpub%0(%1) forward%0(%1);public%0(%1)

fpub Snippet1() // Отрывок кода #1, без использования sizeof.
{
new a[10];
#pragma unused a
return 10;
}

fpub Snippet2() // Отрывок кода #2, с использованием sizeof.
{
new a[10];
#pragma unused a
return sizeof(a);
}


Скомпилируем скрипт с ключом "-a" для получения ассемблерного листинга:


CODE 0 ; 0
;program exit point
halt 0

proc ; Snippet1
; line 4
; line 5
break ; c
;$lcl a ffffffd8
stack ffffffd8
zero.pri
addr.alt ffffffd8
fill 28
; line 7
break ; 2c
const.pri a
stack 28
retn

proc ; Snippet2
; line b
; line c
break ; 48
;$lcl a ffffffd8
stack ffffffd8
zero.pri
addr.alt ffffffd8
fill 28
; line e
break ; 68
const.pri a
stack 28
retn


STKSIZE 1000

Обратите внимание на Snippet1 и Snippet2: опкоды в этих функциях абсолютно одинаковые.
Как и было сказано ранее, результат работы оператора sizeof был вычислен компилятором и подставлен на место его применения в виде числовой константы.
Поскольку сгенерированный код в обоих отрывках совпадает, тесты скорости не нужны, т.к. производительность у них тоже будет одинаковой (разница будет равняться погрешности метода тестирования).

Теперь проверим утверждение про замедление работы компилятора. Для этого сгенерируем два скрипта, в одном из которых будет 1000 функций, которые возвращают результат, вычисляемый с помощью sizeof, а в другом 1000 функций, которые возвращают обычные числа.
Первый скрипт будет выглядеть примерно так:

new a[128];
#pragma unused a

forward Function0();public Function0()
return 128;

forward Function1();public Function1()
return 128;

forward Function2();public Function2()
return 128;

//...

Примерно так же будет выглядеть второй скрипт, только вместо "128" будет возвращаться "sizeof(a)".
Можно было использовать макрос fpub для сокращения объявления функции, как в первом примере, но это увеличит время препроцессинга скрипта, а вместе с ним и общее время компиляции.

Ещё для ускорения компиляции можно использовать ключ "-a", чтобы компилятор не генерировал файл .amx, а выдавал ассемблерный листинг (.asm).
Всё равно при обычной компиляции этот листинг тоже генерируется, а на его основе уже создаётся файл .amx.
Указав ключ "-a" мы избавим компилятор от бремени составления файла .amx, тем самым ещё больше увеличив время компиляции.

Также можно указать ключи "-(+" и "-;+", чтобы сделать обязательными скобки для параметров функций и точку с запятой в конце каждого предложения (по умолчанию они не обязательны, но разработчики SA:MP зачем-то решили форсировать их обязательное использование, сделав так, чтобы редактор pawno сам запускал компилятор с этими ключами). Кроме того, не лишним будет компилятор "-v0", чтобы компилятор не выводил никаких сообщений.

Принцип работы теста:
Генерируются скрипты test1.pwn и test2.pwn с 10 000 функций в каждом.
В одном из них функции возвращают значение в виде целочисленной константы, в другом значение определяется с помощью оператора sizeof.
Сервер с помощью плагина pawnCompiler (http://forum.sa-mp.com/showthread.php?t=174033) запускает компилятор Pawn.
Запуск происходит асинхронно, поэтому чтобы определить момент завершения компиляции, нужно проверять существование файла с расширением *.asm - как только этот файл будет готов, компилятор завершит свою работу.
Чтобы во время постоянных проверок на существование файла .asm сервер не создавал большую нагрузку на процессор (~50%) и не мешал компилятору, следует перед каждой проверкой использовать оператор sleep.
Т.к. при частом использовании этого оператора можно добиться ошибки "stack/heap collision", лучше использовать функцию dc_SleepFix (http://pro-pawn.ru/showthread.php?13188-dc_SleepFix).
Как только замеры компиляции времени будут готовы, вывести их вместе с разницей во времени, как общей, так и на одно использование sizeof.



#include <a_samp>
#include "../include/pawnCompiler.inc"

#define TEST_SCRIPT_1 "test1"
#define TEST_SCRIPT_2 "test2"
#define TEST_SCRIPT_PWN_1 TEST_SCRIPT_1 ".pwn"
#define TEST_SCRIPT_PWN_2 TEST_SCRIPT_2 ".pwn"
#define TEST_SCRIPT_ASM_1 TEST_SCRIPT_1 ".asm"
#define TEST_SCRIPT_ASM_2 TEST_SCRIPT_2 ".asm"
#define TEST_ARRAY_SIZE 128
#define TEST_FUNCTIONS_COUNT 10_000


GenerateScript(name[], bool:use_sizeof)
{
static const header[] =
"/* !! This file has been generated automatically. !! */" "\n"\
"\n"\
"new a[" #TEST_ARRAY_SIZE "];" "\n"\
"#pragma unused a" "\n"\
"\n";
static const function_str0[] = "forward Function";
static const function_str1[] = "();public Function";
static const function_str2[] = "()return ";
static const function_str3[2][] =
{
{"" #TEST_ARRAY_SIZE ";\n"},
{"sizeof(a);\n"}
};
new File:handle = fopen(name, io_write);
fwrite(
handle,
header
);
new num_buffer[11];
for (new i = 0; i < TEST_FUNCTIONS_COUNT; ++i)
{
fwrite(handle, function_str0);
valstr(num_buffer, i);
fwrite(handle, num_buffer);
fwrite(handle, function_str1);
fwrite(handle, num_buffer);
fwrite(handle, function_str2);
fwrite(handle, function_str3[!!use_sizeof]);
}
fclose(handle);
}

dc_SleepFix(time) // by Daniel_Cortez \\ pro-pawn.ru
{ // WARNING: This function is still only usable in main().
// It just fixes the stack/heap collision error in default sleep().
static heap_ptr, stack_ptr;
#emit lctrl 2
#emit stor.pri heap_ptr
#emit lctrl 4
#emit stor.pri stack_ptr
sleep(time);
#emit load.pri stack_ptr
#emit sctrl 4
#emit load.pri heap_ptr
#emit sctrl 2
}
#if defined _ALS_sleep
#undef sleep
#else
#define _ALS_sleep
#endif
#define sleep(%0) dc_SleepFix(%0)

main()
{
GenerateScript(!TEST_SCRIPT_PWN_1, false);
GenerateScript(!TEST_SCRIPT_PWN_2, true);
fremove(TEST_SCRIPT_ASM_1);
fremove(TEST_SCRIPT_ASM_2);
new t1, t2, t;
t = GetTickCount();
WinExec("pawno/pawncc.exe -(+ -;+ -v0 -a scriptfiles/" TEST_SCRIPT_PWN_1);
do { sleep(0); } while (0 == fexist(TEST_SCRIPT_ASM_1));
t1 = GetTickCount() - t;
t = GetTickCount();
WinExec("pawno/pawncc.exe -(+ -;+ -v0 -a scriptfiles/" TEST_SCRIPT_PWN_2);
do { sleep(0); } while (0 == fexist(TEST_SCRIPT_ASM_2));
t2 = GetTickCount() - t;
new Float:dt = float(t2 - t1) / float(TEST_FUNCTIONS_COUNT);
printf("Компиляция без sizeof: %d", t1);
printf("Компиляция с использованием sizeof: %d", t2);
printf(
"dt = %d / %d = %.4f тиков = %.8f мс",
t2 - t1, TEST_FUNCTIONS_COUNT, dt, dt / 1000.0
);
}


Результат:

Компиляция без sizeof: 8484
Компиляция с использованием sizeof: 9741
dt = 1257 / 10000 = 0.1476 тиков = 0.00014769 мс

Получается, при каждом использовании sizeof время компиляции увеличивается примерно на ~0.00015 миллисекунды или на ~147 микросекунд - крайне малый отрезок времени.
Измерения были получены на ПК с процессором Intel Pentium E6600, 3.06 ГГц. Сейчас всё чаще можно встретить ПК и помощнее, поэтому у вас разница может оказаться ещё меньше.
Можно больше времени потерять на подсчёт размера массива вручную, чем на компиляцию с sizeof.
Как результат, миф о вреде использования sizeof опровергнут.


Вывод: sizeof не влияет на производительность кода, а на время компиляции воздействие ничтожно мало и им можно принебречь.



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