Добро пожаловать на Pro Pawn - Портал о PAWN-скриптинге.
Показано с 1 по 2 из 2
  1. #1
    Аватар для Tracker1
    Проверенный

    Статус
    Оффлайн
    Регистрация
    30.07.2013
    Сообщений
    54
    Репутация:
    84 ±

    Почему не следует использовать длинные строки в Pawn

    Вступление:
    У людей есть весьма необычные взгляды на строки в ПАВН, особенно на их длинну. Много людей думают что стринги не могут быть длинее 256*, почему, я незнаю, похоже на произвольно введенный лимит для них без нужды, когда на самом деле лимита нету. Другие люди думают что все стринги должны быть 256 без причины, неважно насколько длинно они создают стринг. Стринг в ПАВН тоже самое что и массив, у людей вроде нет больших проблем с использованием массивом, так почему же есть со стрингами?
    *заметьте что много людей используют либо 255 либо 256, но это так же глупо.

    Предпосылка
    Стринг в павне это просто массив символов, без отличия от других массивов. Стринги заканчиваются NULL'ом, это занчит что они все имеют символ \0 (NULL) на конце (ASCII код 0, не путать с символом "0" у которого код 4). Если у вас такая вот строка
    PHP код:
    new str[3] = "hi"
    То на самом деле она использует 3 ячейки. Одна для h, другая для i, и последня для символа NULL, что сигнализирует об окончании строки. Если копнуть глубже, то это выглядит вот так
    PHP код:
    new str[3] = {'h''i''\0'); 
    Или так
    PHP код:
    new str[3] = {104,105,0}; 
    Где 104 и 105 это ASCII код для символов 'h' и 'i' соотвественно, и 0 - код для NULL символа.

    В павн по дефолту все переменные равняются 0 когда вы их обьявляете, это значит что когда вы делаете.
    PHP код:
    new str[256]; 
    На самом деле вы делаете
    PHP код:
    new str[256];
    for(new 
    i=0;i<256;i++)
    {
        
    str[i]=0;

    Оптимизации больше, но это все равно требует времени

    Так почему же вы не должны использовать 256?
    Во-первых - медленно

    Как я обьяснил в предпосылке все переменные ставятся на 0, когда мы их задаем, больше переменных - больше нагрузка на сервер чтобы он выставил их всех на 0. Этим занимается оптимизированная сборка, не интепретатор PAWN, спешу заметить что в AMXModX переменные не отчищаются при обьявлении, и они страдают проблемами из-за этого.*

    *Заметка переводчика: текст йоба, могут быть проблемы с восприятием

    Во-вторых - вам это не надо

    Рассмотрим пост который я недавно видел
    Quote: Originally Posted by X_Cutterz
    PHP код:
    stock ReturnModeratorCmd(playerid,reqlvl)
    {
        new 
    rmcmd[256];
        
    format(rmcmd,sizeof(rmcmd),"Only moderators level %d+ can use this command!",reqlvl);
        return 
    SendClientMessage(playerid,Green,rmcmd);

    Предположим что максимум админ уровня это 10000, чтобы не запутаться (0 до 9999), таким образом самый длинный номер который будет вставлен в стринг состоит из 4 символов.

    Quote: Only moderators level %d+ can use this command!

    Но мои подсчеты подсказывают мне что их здесь всего 47, 2 из которых это %d которые не появятся в окончательном стринге, получается 45 уходят в окончательную строку. Мы уже убедились что самое длинное записываемое число будет длинной в 4(кстати, самое длинное число в павн может быть длинной в 11 символов (-2147483647)), и мы знаем что все строки нуждаются в NULL на конце, поэтому в этой строке максимальная длинна:

    47 - 2 + 4 + 1 = 50

    50 ячеек, и зачем вам 256? Это просто трата впустую 206 ячеек (824 байта - почти килобайт потерянной памяти)

    Максимальное поле ввода - 128

    В SA-MP чате максимальная длинна линий - 128, если кто-то что-то вводит, вы может быть уверены что это никогда не будет длинее 128. Это включает текст и комманды, так для чего же выделять массив который в 2 раза больше чем длинна вводимой строки?


    Вам на самом деле это не надо

    PHP код:
    public OnPlayerCommandText(playeridcmdtext[])
    {
        new
            
    string[256],
            
    cmd[256];
        
    cmd strtok(cmdtextidx);
        if (
    strcmp(cmd"/num"true) == 0)
        {
            
    format(stringsizeof (string), "Random number: %d"random(27));
            
    SendClientMessage(playerid0xFF0000AAstring);
        }

    Достаточно много смысловых ошибок в этом коде, но к сожалению это встречается ОЧЕНЬ часто. Игнорируем то что они используют strtok, сейчас речь не об этом. Код ОЧЕНЬ плохой:

    PHP код:
    new string[256],cmd[256]; 
    Зачем два? Зачем вам нужен один огромный стринг для комманды и другой огромный стринг для данных которые вы будете обрабатывать после ввода команды, когда данные в переменной cmd уже ненужны? Просто используйте это еще раз, в ином случае вы создаете двойную нагрузку.

    PHP код:
     new string[256]; 
    Почему 256? Как я уже обьяснил вводимая строка не может быть больше 128. А в коде выше можно сказать из-за заносимой строки в стринг нам не понадобится ячеек больше чем 18.

    Более разумная версия всего происходящего будет выглядеть вот так:

    PHP код:
    public OnPlayerCommandText(playeridcmdtext[])
    {
        new
            
    string[128]; // cmdtext НИКОГДА НЕ БУДЕТ ДЛИНЕЕ 128!!!
        
    string strtok(cmdtextidx);
        if (
    strcmp(string"/num"true) == 0)
        {
            
    format(stringsizeof (string), "Random number: %d"random(27)); // Нам надо 18 ячеек есть, однако так как вверху нам требовалось 128, все не так уж и плохо
            
    SendClientMessage(playerid0xFF0000AAstring);
        }


    Использование Stack'а

    Когда вы вызываете функцию, память используемая в ней хранится на штуке называемой stack(или Heap, но они используют одинаковый размер памяти, так что для упрощения мы называем stack), память выделяемая функции не может быть выделена статично, потому что функция может вызывать саму себя*, тем самым вынуждая выделенную память двоится. Та зона памяти где хранятся данные функция называется dynamic(динамичная) память. Например:

    PHP код:
    new
        
    g_var 2;
    stock StackUse()
    {
        new
            
    str[9];
        
    format(strsizeof (str), "g_var = %d"g_var);
        
    SendClientMessageToAll(0xFF0000AAstr);
        if (
    g_var)
        {
            
    g_var--;
            
    StackUse();
        }

    Каждый вызов этой функции выделяет 9 ячеек в stack для использования, а затем она вызовет сама себя и выделит еще 9 ячеек, и еще раз она вызывет сама себя и выделит 9 ячеек. В сумме 3 выполнении, у нас выделено 27 ячеек в stack'е. На третье выполнение g_var становится равным 0, поэтому функция не вызывает себя и заканчивает свое действие, удаляя 9 ячеек из stack'а. Функция передает контроль прошлой инстанции этой функции, которая так же заканчивается и высвобождает 9 ячеек из stack'а, так же поступает и первая инстанция. В итоге теперь у нас занято 0 ячеек.

    А теперь представьте этот код
    PHP код:
    new
        
    g_var 2;
    stock StackUse()
    {
        new
            
    str[256];
        
    format(strsizeof (str), "g_var = %d"g_var);
        
    SendClientMessageToAll(0xFF0000AAstr);
        if (
    g_var)
        {
            
    g_var--;
            
    StackUse();
        }

    Тоже самое, но теперь она, при вызове ее 3 раза, выделит 768 ячеек (3 килобайта) в stack'е, сравнивая с оригинальными 27 ячейками (0,1 килобайт, кпд больше чем 2800%).

    Некотоыре из вас могли замечать такие сообщения при компиляции
    Код:
    Header size:      216 bytes
    Code size:       776 bytes
    Data size:       528 bytes
    Stack/heap size:   16384 bytes; estimated max. usage: unknown, due to recursion
    Total requirements:  17904 bytes
    или
    Код:
    Header size:      200 bytes
    Code size:       588 bytes
    Data size:       512 bytes
    Stack/heap size:   16384 bytes; estimated max. usage=10250 cells (41000 bytes)
    Total requirements:  17684 bytes
    Это значит что компилятор обнаружил что вы используете больше места в stack'е чем доступно. Много важной информации хранится в stack'е, такой как техническая информация о текущей функции, из-за которой PAWN знает куда возвращаться. Используя слишком много памяти, вы можете переписать информацию в stack, возвращая(returning) в случайную точку в коде, что означает абсолютный крэш. В конце концов ваша информация может быть испорчена, когда ее перезапишет другая информация. Когда люди получают эти ошибки стандартный совет использовать #pragma dynamic, который служит обходом проблемы, не решением. Я уверен что застану вас в расплох, сказав то что YSI не вызывает этой ошибки, не смотря на его огромный размер, но маленькие скрипты нубоскриптеров вызывают. А ваш скрипт вызывает ли эту ошибку?

    Прошу заметить что в 1 ошибке что я продемонстрировал, значится что вы используете больше stack'а чем положено, но так как функции вызывают сами себя(рекурсия епта), компилятор не может сказать точно сколько stack'а использует скрипт. Заметьте что компилятор не может самостоятельно рассчитать кол-во запусков функции. Во второй ошибке рекурсии нету.

    Это относится ко всем массивам, но к стрингам в первую очередь.

    Запакованные стринги *запакованные - сжатые.
    Фишка PAWN которая очень редко используется в SA:MP это запакованные стринги. Обычные стринги содержат 1 символ в каждой ячейке, а ячейка это 4 байта. Запакованные стринги же хранят по 4 символа на каждую ячейку.

    Не запакованные:
    PHP код:
     new string[12] = "Hello there"// 12 cells, 48 bytes 
    Запакованные
    PHP код:
     new string[12 char] = !"Hello there"// 3 cells, 12 bytes 
    Такие стринги хорошо описаны в pawn-lang.pdf, поэтому я здесь долго не задерживаюсь, но если у вас большие массивы стрингов, то это было бы очень хорошо, если вы будете использовать сжатые стринги для хранения, не для манипуляции. Если бы этот метод использовался в ReturnModeratorCmd выше, то кпд было бы больше 2000%(1 килобайт к 50 байтам)

    Когда я должен использовать 256?

    Говоря все это, хочу заметить что есть такие случаи, когда большие массивы необходимы.

    1. SQL
      Запросы в SQL могут быть очень длинными. Мне когда-то потребовался стринг размером в 1024 ячейки, однако это просто исключение из правила "Вам это не нужно", в этом случае вам это НУЖНО.
    2. File Reading
      Когда вы читаете файл, нет уверенности в длинне строки. Будет неплохо использовать стринг размером в 256.

    Заключение

    Если сомневаешься - пропусти это*. Бывают такие случаи, когда огромные массивы удобны для использования, думайте каждый раз когда вы объявляете массив. Думайте о том что вы собираетесь с ним делать

  2. 3 пользователя(ей) сказали cпасибо:
    $continue$ (08.07.2015) Osetin (03.08.2013) [ForD] (21.02.2015)
  3. #2
    Аватар для Osetin
    •Администратор•

    Статус
    Оффлайн
    Регистрация
    26.03.2013
    Адрес
    ♔Osetia, Vladikavkaz♔
    Сообщений
    3,432
    Репутация:
    1093 ±
    Неплохо

 

 

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

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

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

Ваши права

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