На самом деле в названии мифа сразу две ошибки.
Во-первых, 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 запускает компилятор Pawn.
Запуск происходит асинхронно, поэтому чтобы определить момент завершения компиляции, нужно проверять существование файла с расширением *.asm - как только этот файл будет готов, компилятор завершит свою работу.
- Чтобы во время постоянных проверок на существование файла .asm сервер не создавал большую нагрузку на процессор (~50%) и не мешал компилятору, следует перед каждой проверкой использовать оператор sleep.
Т.к. при частом использовании этого оператора можно добиться ошибки "stack/heap collision", лучше использовать функцию 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); handle,
header
);
new num_buffer[11];
for (new i = 0; i < TEST_FUNCTIONS_COUNT; ++i)
{
fwrite(handle
, function_str0
); fwrite(handle
, function_str1
); fwrite(handle
, function_str2
); fwrite(handle
, function_str3
[!!use_sizeof
]); }
}
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);
new t1, t2, t;
WinExec("pawno/pawncc.exe -(+ -;+ -v0 -a scriptfiles/" TEST_SCRIPT_PWN_1);
do { sleep(0); } while (0 == fexist(TEST_SCRIPT_ASM_1
)); WinExec("pawno/pawncc.exe -(+ -;+ -v0 -a scriptfiles/" TEST_SCRIPT_PWN_2);
do { sleep(0); } while (0 == fexist(TEST_SCRIPT_ASM_2
)); new Float:dt
= float(t2
- t1
) / float(TEST_FUNCTIONS_COUNT
); printf("Компиляция без sizeof: %d", t1
); printf("Компиляция с использованием sizeof: %d", t2
); "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
опровергнут.