Добро пожаловать на Pro Pawn - Портал о PAWN-скриптинге.

Реклама



**Как получить V.I.P** (Перейти)
Чтобы заказать рекламу на Pro-Pawn.Ru, обращайтесь в Skype.
Баннерная реклама 100руб/мес, Текстовая 50руб/мес.
Показано с 1 по 1 из 1
  1. #1
    Аватар для Daniel_Cortez
    new fuck_logic[0] = EOS;

    Статус
    Оффлайн
    Регистрация
    06.04.2013
    Адрес
    Novokuznetsk, Russia
    Сообщений
    1,559
    Репутация:
    2062 ±

    Мифы о Pawn-скриптинге - #6

    Внимание: данная тема закрыта для защиты от копирования.
    Если есть какие-то вопросы, замечания или просто пожелания по поводу данного урока - оставляйте их здесь.


    Миф 6: "Не стоит измерять длину строк по формуле с sizeof: это неудобно, замедляет компиляцию, увеличивает расход памяти, нагружает сервер лишними расчётами."

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

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

    Доказательство:
      Открыть/закрыть

    Скажу сразу, я не буду сравнивать расчёт по формуле с быдлокодерским "new shit[144];". В обоих случаях будет именно подсчёт размера строки, а не впихивание везде одного и того же магического числа, т.е. сравнение будет производиться в равных условиях и при одинаковой постановке задачи.

    Итак, допустим, у нас есть 2 образца кода:
    PHP код:
    CMD:myinfo(playeridparams[])
    {
        new 
    string[57];
        
    GetPlayerName(playeridstringsizeof(string));
        
    format(stringsizeof(string), "Ваш ID: %d, никнейм: %s"playeridstring);
        return 
    SendClientMessage(playerid, -1string);

    PHP код:
    CMD:myinfo(playeridparams[])
    {
        static const 
    fmt_str[] = "Ваш ID: %d, никнейм: %s";
        new 
    string[sizeof(fmt_str) + (-5) + (-MAX_PLAYER_NAME)];
        
    GetPlayerName(playeridstringsizeof(string));
        
    format(stringsizeof(string), fmt_strplayeridstring);
        return 
    SendClientMessage(playerid, -1string);


    Удобство.
    Сценарий первый: нужно сделать расчёт размера массива под форматирование строки.

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

    Но если считать по формуле с sizeof (отрывок #2), то компилятор сам подсчитает размер форматной строки (не длину, а именно размер, т.к. в него уже включена +1 ячейка под конец строки).
    Затем каждое форматируемое значение следует добавить в формулу по уже проработанному шаблону "+ (-<размер спецификатора> + <макс. размер значения>)".
    Получившаяся формула скармливается компилятору и он сам всё считает, тем самым исключая арифметические ошибки (от них никто не застрахован).
    Кроме того, формула подсчёта расписана в коде - можно посмотреть на неё ещё раз и перепроверить её правильность, что исключает оставшуюся вероятность возникновения ошибок из-за неправильного составлении формулы.
    Придётся написать одну лишнюю строку кода, чтобы объявить строковую константу для её использования в sizeof, но зато вам не придётся напрягаться с расчётами, при этом обеспечив минимальное использование стека и надёжность расчётов.


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

    При подсчёте по формуле, если в форматной строке не были изменены спецификаторы (а именно так чаще всего и бывает), то и пересчитывать ничего не нужно - вы уже объяснили компилятору, по какой формуле нужно считать, и он сам всё сделает.

    Если же считать вручную, размер массива придётся пересчитывать, если изменились спецификаторы или длина форматной строки. Одно из них меняется почти всегда, поэтому пересчёт размера массива потребуется практически в любом случае.


    Скорость компиляции.
    Как и в предыдущем мифе, попробуем сгенерировать два скрипта с большим количеством функций, в одном скрипте размером форматного буфера будет обычное число, во втором - формула.
    В качестве повторяемой функции можно использовать оба варианта команды /myinfo, приведённые в самом начале доказательства. Количество повторов: 5000.
    Я добавил в код теста несколько незначительных изменений, но думаю нет смысла описывать их все.
    PHP код:
    #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_SCRIPT_AMX_1        TEST_SCRIPT_1 ".amx"
    #define    TEST_SCRIPT_AMX_2        TEST_SCRIPT_2 ".amx"
    #define    TEST_FUNCTIONS_COUNT    5_000


    GenerateScript(name[], bool:use_formula)
    {
        static const 
    header[] =
            
    "/* !! This file has been generated automatically. !! */" "\n"\
            
    "\n"\
            
    "#include <a_samp>" "\n"\
            
    "\n"\
            
    "\n";
        static const 
    function_str0[] = "public Function";
        static const 
    function_str1[] = "(playerid, params[]);\npublic Function";
        static const 
    function_str2[] = "(playerid, params[])\n{\n";
        static const 
    function_str3_0[] =
            
    "\t" "new string[47];" "\n"\
            
    "\t" "GetPlayerName(playerid, string, sizeof(string));" "\n"\
            
    "\t" "format(string, sizeof(string), \"ID: %d, nickname: %s.\", playerid, string);" "\n"\
            
    "\t" "return SendClientMessage(playerid, -1, string);";
        static const 
    function_str3_1[] =
            
    "\t" "static const fmt_str[] = \"ID: %d, nickname: %s.\";" "\n"\
            
    "\t" "new string[sizeof(fmt_str) + (-2 + 5) + (-2 + MAX_PLAYER_NAME)];" "\n"\
            
    "\t" "GetPlayerName(playerid, string, sizeof(string));" "\n"\
            
    "\t" "format(string, sizeof(string), fmt_str, playerid, string);" "\n"\
            
    "\t" "return SendClientMessage(playerid, -1, string);";
        static const 
    function_str4[] =
            
    "\n"\
            
    "}" "\n";
        new 
    File:handle fopen(nameio_write);
        
    fwrite(
            
    handle,
            
    header
        
    );
        new 
    num_buffer[11];
        for (new 
    0TEST_FUNCTIONS_COUNT; ++i)
        {
            
    fwrite(handlefunction_str0);
            
    valstr(num_bufferi);
            
    fwrite(handlenum_buffer);
            
    fwrite(handlefunction_str1);
            
    fwrite(handlenum_buffer);
            
    fwrite(handlefunction_str2);
            
    fwrite(handleuse_formula function_str3_1 function_str3_0);
            
    fwrite(handlefunction_str4);
        }
        
    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_ptrstack_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_1false);
        
    GenerateScript(!TEST_SCRIPT_PWN_2true);
        
    fremove(TEST_SCRIPT_ASM_1);
        
    fremove(TEST_SCRIPT_ASM_2);
        
    fremove(TEST_SCRIPT_AMX_1);
        
    fremove(TEST_SCRIPT_AMX_2);
        new 
    t1t2t;
        
    GetTickCount();
        
    WinExec("pawno/pawncc.exe -(+ -;+ -v0 -O1 -d0 -a scriptfiles/" TEST_SCRIPT_PWN_1);
        do { 
    sleep(0); } while (== (fexist(TEST_SCRIPT_ASM_1) | fexist(TEST_SCRIPT_AMX_1)));
        
    t1 GetTickCount() - t;
        
    GetTickCount();
        
    WinExec("pawno/pawncc.exe -(+ -;+ -v0 -O1 -d0 -a scriptfiles/" TEST_SCRIPT_PWN_2);
        do { 
    sleep(0); } while (== (fexist(TEST_SCRIPT_ASM_2) | fexist(TEST_SCRIPT_AMX_2)));
        
    t2 GetTickCount() - t;
        new 
    Float:dt float(t2 t1) / float(TEST_FUNCTIONS_COUNT);
        
    printf("Компиляция без расчёта по формуле: %d"t1);
        
    printf("Компиляция с расчётом по формуле: %d"t2);
        
    printf(
            
    "dt = %d / %d = %.4f тиков = %.8f мс",
            
    t2 t1TEST_FUNCTIONS_COUNTdtdt 1000
        
    );

    На моём ПК тест выдал следующий результат:
    Код:
    Компиляция без расчёта по формуле: 16577
    Компиляция с расчётом по формуле: 18119
    dt = 1542 / 5000 = 0.3084 тиков = 0.00030839 мс
    Итак, разница от расчётов размера буфера по формуле составила ~300 микросекунд.


    Расход памяти и нагрузка на сервер.

    Модифицируем приведённый выше тест, изменив число повторов функций с 2000 до 1.
    PHP код:
    #define    TEST_FUNCTIONS_COUNT    1 
    После этого снова запустим тестовый скрипт. В результате в папке scriptfiles будут сгенерированы файлы test1.pwn и test2.pwn.
      Открыть/закрыть

    test1.pwn:
    PHP код:
    /* !! This file has been generated automatically. !! */

    #include <a_samp>

    public Function0(playeridparams[]);
    public 
    Function0(playeridparams[])
    {
        new 
    string[47];
        
    GetPlayerName(playeridstringsizeof(string));
        
    format(stringsizeof(string), "ID: %d, nickname: %s."playeridstring);
        return 
    SendClientMessage(playerid, -1string);

    test2.pwn:
    PHP код:
    /* !! This file has been generated automatically. !! */

    #include <a_samp>

    public Function0(playeridparams[]);
    public 
    Function0(playeridparams[])
    {
        static const 
    fmt_str[] = "ID: %d, nickname: %s.";
        new 
    string[sizeof(fmt_str) + (-5) + (-MAX_PLAYER_NAME)];
        
    GetPlayerName(playeridstringsizeof(string));
        
    format(stringsizeof(string), fmt_strplayeridstring);
        return 
    SendClientMessage(playerid, -1string);


    Кроме них сгенерируются файлы test1.asm и test2.asm.
      Открыть/закрыть

    test1.asm:
    Код:
    CODE 0	; 0
    ;program exit point
    	halt 0
    
    	proc	; Function0
    	; line 9
    	;$lcl params 10
    	;$lcl playerid c
    	; line a
    	;$lcl string ffffff44
    	stack ffffff44
    	zero.pri
    	addr.alt ffffff44
    	fill bc
    	; line b
    	push.c 2f
    	;$par
    	push.adr ffffff44
    	;$par
    	push.s c
    	;$par
    	push.c c
    	sysreq.c 0	; GetPlayerName
    	stack 10
    	;$exp
    	; line c
    	push.adr ffffff44
    	;$par
    	push.adr c
    	;$par
    	push.c 0
    	;$par
    	push.c 2f
    	;$par
    	push.adr ffffff44
    	;$par
    	push.c 14
    	sysreq.c 1	; format
    	stack 18
    	;$exp
    	; line d
    	push.adr ffffff44
    	;$par
    	push.c ffffffff
    	;$par
    	push.s c
    	;$par
    	push.c c
    	sysreq.c 2	; SendClientMessage
    	stack 10
    	stack bc
    	retn
    
    
    DATA 0	; 0
    dump 49 44 3a 20 25 64 2c 20 6e 69 63 6b 6e 61 6d 65 
    dump 3a 20 25 73 2e 0 
    
    STKSIZE 1000
    test2.asm:
    Код:
    CODE 0	; 0
    ;program exit point
    	halt 0
    
    	proc	; Function0
    	; line 9
    	;$lcl params 10
    	;$lcl playerid c
    	; line a
    	; line b
    	;$lcl string ffffff44
    	stack ffffff44
    	zero.pri
    	addr.alt ffffff44
    	fill bc
    	; line c
    	push.c 2f
    	;$par
    	push.adr ffffff44
    	;$par
    	push.s c
    	;$par
    	push.c c
    	sysreq.c 0	; GetPlayerName
    	stack 10
    	;$exp
    	; line d
    	push.adr ffffff44
    	;$par
    	push.adr c
    	;$par
    	push.c 0
    	;$par
    	push.c 2f
    	;$par
    	push.adr ffffff44
    	;$par
    	push.c 14
    	sysreq.c 1	; format
    	stack 18
    	;$exp
    	; line e
    	push.adr ffffff44
    	;$par
    	push.c ffffffff
    	;$par
    	push.s c
    	;$par
    	push.c c
    	sysreq.c 2	; SendClientMessage
    	stack 10
    	stack bc
    	retn
    
    
    DATA 0	; 0
    dump 49 44 3a 20 25 64 2c 20 6e 69 63 6b 6e 61 6d 65 
    dump 3a 20 25 73 2e 0 
    
    STKSIZE 1000

    Итак, что же мы видим? В обоих скриптах опкоды совершенно одинаковые, за исключением комментариев о номерах строк в исходном файле.
    Содержимое секции данных (после слова "DATA") тоже идентично.

    Теперь немного изменим код теста, убрав параметр компилятора "-a", чтобы компилятор генерировал файл *.amx вместо *.asm.
    Также заменим "-v0" на "-v2", чтобы он выдавал отчёт о результатах компиляции.
    До:
    Код:
    WinExec("pawno/pawncc.exe -(+ -;+ -v0 -O1 -d0 -a scriptfiles/" TEST_SCRIPT_PWN_1);
    После:
    Код:
    WinExec("pawno/pawncc.exe -(+ -;+ -v2 -O1 -d0 scriptfiles/" TEST_SCRIPT_PWN_1);
    После этого снова скомпилируем и запустим скрипт. В консоли сервера последовательно появятся отчёты о компиляции первого и второго скриптов.
    Код:
    Header size:            196 bytes
    Code size:              212 bytes
    Data size:               88 bytes
    Stack/heap size:      16384 bytes; estimated max. usage=59 cells (236 bytes)
    Total requirements:   16880 bytes
    
    Done.
    Header size:            196 bytes
    Code size:              212 bytes
    Data size:               88 bytes
    Stack/heap size:      16384 bytes; estimated max. usage=59 cells (236 bytes)
    Total requirements:   16880 bytes
    
    Done.
    Результаты абсолютно одинаковые, что было вполне ожидаемо, т.к. ассемблерные листинги тоже совпадали.


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

    Кому-то удобен первый способ, кому-то второй, поэтому чьи-либо суждения об удобстве/неудобстве одного из этих них сугубо субъективны (чего, кстати, нельзя сказать о надёжности расчётов) и не могут быть восприняты всеми одинаково.
    К тому же, как уже было сказано ранее, недостатки есть у обоих способов и доказывать однозначное превосходство одного из них нет смысла. У каждого из них есть своя аудитория, которой удобно использовать данный конкретный способ расчёта.
    Поэтому утверждение о неудобстве расчётов по формуле в корне неправильно: что неудобно для одного, может оказаться очень даже удобно для другого.

    При расчёте размера форматного буфера по формуле, разумеется, тратится больше времени, чем при объявлении массива с размером в виде одиночной числовой константы, т.к. компилятору нужно разобрать формулу и вычислить её результат.
    Выявленная разница во времени при компиляции с расчётом размеров буфера по формуле - ~300 микросекунд или 0.0003 секунды - и это для целых 5000 расчётов, когда вряд ли на практике существует хотя бы один проект, в котором расчёт по формуле можно применить более 1000 раз.
    Ничтожно малое время, особенно в сравнении с общим временем компиляции.
    Итак, время, затрачиваемое на компиляцию с расчётом по формуле, с трудом поддаётся измерению и ни на миллисекунду не влияет на результат.

    По поводу якобы увеличения нагрузки на сервер и повышения расхода памяти - полная чушь.
    Изучение ассемблерных листингов и отчётов о компиляции скриптов (*.amx) только доказывают факт абсурдности подобных высказываний.
    Но как же так!? Ведь во втором примере были лишние расчёты! Это должно создать лишнюю нагрузку!
    Формула рассчитывается ещё во время компиляции и превращается в числовую константу.
    Т.е. если скормить компилятору "new string[sizeof(fmt_str) + (-2 + 5) + (-2 + MAX_PLAYER_NAME);", он превратит это в "new string[57];" - в точности, как в первом примере.
    Таким образом, на сервере от этого никакой нагрузки нет. Лишняя нагрузка есть только на компилятор, но как было показано выше, она не увеличивает время компиляции ни на одну миллисекунду.
    Но во втором примере ещё был лишний массив! От этого должно использоваться больше памяти!
    В первом примере этот массив тоже был. Любая строка - это тоже массив и для её хранения тоже требуется место в секции данных (а вы думали, что эти строки должны браться из ниоткуда?)
    В примере с расчётом размера по формуле этот массив всего лишь был объявлен в явном виде, на результат компиляции это не повлияло - в обоих примерах строки оказались в секции данных.

    Общий вердикт: миф о вреде расчёта размера строк по формуле опровергнут.
    Индивидуально в PM и Skype по скриптингу не помогаю. Задавайте все свои вопросы здесь (click).
    SA-MP 0.4 is a lie

  2. 9 пользователя(ей) сказали cпасибо:
    $continue$ (15.01.2016)Alanchick (13.07.2016)Anton Styazhkin (15.01.2016)enj0y (03.10.2016)gangzone.ini (15.01.2016)LLIapuk (15.01.2016)MaKcuM (12.01.2017)Profyan (16.01.2016)[ForD] (18.01.2016)
 

 

Информация о теме

Пользователи, просматривающие эту тему

Эту тему просматривают: 1 (пользователей: 0 , гостей: 1)

Ваши права

  • Вы не можете создавать новые темы
  • Вы не можете отвечать в темах
  • Вы не можете прикреплять вложения
  • Вы не можете редактировать свои сообщения
  •