Sign in to follow this  
Followers 0
odosenok

Оптимизация и пессимизация. Сборник советов по оптимизации и написанию кода

3 posts in this topic

1. Введение.

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

 

2. Понятие оптимизации. Пессимизация.

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

 

Что же касается пессимизации? Опять же, это те действия, которые либо никак не повлияют на производительность и затрату ресурсов (могут повлиять, но крайне незначительно, потому выгодой можно пренебречь), либо повлияют отрицательно. В любом случае в процессе пессимизации Вы попусту теряете свое время. Вы можете усовершенствовать какой-либо код не один день и не одну неделю, желая улучшить его, и добиться своего результата: увеличить быстроту выполнения данного кода на доли секунды. Тогда назревает вопрос: а стоили эти дни или даже недели того, чего Вы добились?

 

3mznbdCRJv4.jpg

 

3. Подводим черту теоретической части.

Несомненно, приятно видеть свой код, который не только отлично работает (речь идет о быстроте), но и затраты при работе которого сведены к минимуму. Но стоит уяснить два немаловажных правила оптимизации:

  1. Прежде, чем начать оптимизировать, поставьте для себя цель. Например, "следует оптимизировать расход памяти". И, при такой оптимизации, акцент делайте именно на экономию памяти.
  2. Наибольшую выгоду при оптимизации можно извлечь не при улучшении отдельных участков кода, а при улучшении алгоритмов работы тех или иных систем. Зачастую неправильный алгоритм ведет за собой нерациональный расход ресурсов и медленную обработку кода.

 

4. Советы по оптимизации кода.

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

 

Убедительная просьба! Старайтесь не развивать дискуссий в данной теме. Это наведет в некотором роде беспорядок внутри темы, чего, конечно же, мы не хотим. Для обсуждения пунктов и вопросов создавайте новые темы в разделе "Вопросы по скриптингу", где мы с радостью ответим на все Ваши вопросы.

Заметка от DEST , создано

Восстановлено

Share this post


Link to post
Share on other sites

#1. Подсчет символов в строке.

Довольно-таки часто можно встретить пример, когда для строки небольшого размера выделяется огромное число ячеек. Быть может, у Вас возникнет вопрос: а что в этом такого? Прежде всего не стоит забывать, что у сервера размер памяти ограничен, и стандартными путями его изменить нельзя. Когда Вы создаете переменную, часть памяти уходит именно под эту переменную. И, если таких переменных и их размер внутри одного участка кода хватает на то, чтобы заполнить память сервера, он завершает свою работу, а в логах появляется неприятная строка: 

Run time error 3: "Stack/heap collision (insufficient stack size)"

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

 

Пример неудачной реализации кода с подсчетом размера "от фонаря":

new string[256];
format(string, sizeof string, "С 01.01.1970 прошло %d секунд", gettime());
SendClientMessageToAll(-1, string);

Пример исправленной версии данного кода:

new string[29+(-2+10)+1];
format(string, sizeof string, "С 01.01.1970 прошло %d секунд", gettime());
SendClientMessageToAll(-1, string);

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

 

Способы подсчета символов в строке:

  1. При помощи нашего сервиса
  2. Если Вы используете редакторы, отличные от Pawno (например, NotePad++, Visual Studio Code, Microsoft Visual Studio, Sublime Text3 и так далее), достаточно выделить содержимое строки - в низу появится информация о выделенной области, включая количество символов.
  3. Автоматический подсчет символов в строке (метод от Daniel_Cortez).

Share this post


Link to post
Share on other sites

#2. Многократное использование одинакового текста или чисел.

Рассматривая исключительно из интереса слитые в общий доступ игровые моды, можно заменить ситуацию, когда в начале мода присутствуют макросы, содержащие, например, текст о том, что игрок не авторизован.

Спойлер

#define MSG_NOADMIN1(%0)		SendClientMessage(%0, COLOR_GREEN, "Вы не авторизированы!")
#define MSG_NOTPLAYER(%0)		SendClientMessage(%0, COLOR_GREEN, "Игрок с указанным тобой id не найден")
#define MSG_NODRIVER(%0)		SendClientMessage(%0, COLOR_GREEN, "Ты должен находиться в машине и быть за рулём")

 

Казалось бы, упростили себе задачу: теперь не нужно прописывать один и тот же текст огромное число раз, достаточно лишь прописать MSG_NOADMIN1, MSG_NOTPLAYER или MSG_NODRIVER. Однако, данный метод не влияет положительно на расход памяти. Для того, чтобы ответить на вопрос "почему?", следует вспомнить, что такое макрос и что с ним происходит в процессе компиляции.

 

Макрос - директива. Она обрабатывается препроцессором. Грубо говоря, перед началом компиляции свою работу начинает препроцессор, который, помимо ряда других действий, "подставляет" значения на место своего использования.

Спойлер

#define DEFINED_MESSAGE "Your text"
SendDefinedMessage(playerid)
{
	SendClientMessage(playreid, -1, DEFINED_MESSAGE);
}

SendDefinedMessage(playerid)
{
	SendClientMessage(playreid, -1, "Your text");
}

 

И, соответственно, компилироваться будет уже код, в котором отсутствуют директивы.

 

Поэтому, если один и тот же макрос вы используете несколько раз, то препроцессор несколько раз подставляет один и тот же текст в разные участки кода. Каждый такой текст попадает в сегмент данных (секционную память). Соответственно, в конечном итоге в секционную память попадает один и тот же текст неоднократно, что ее явно раздувает. Рассмотрим такой пример на чистом игровом моде. Скомпилируем такой участок кода:

#define TEXT "your text"
public OnGameModeInit()
{
	printf(TEXT);
	printf(TEXT);
	return 1;
}

В информации о компилировании присутствует информация о том, сколько места необходимо для хранения каждого вида информации. То есть в информации о компилировании появляются следующие строки:

Header size:            140 bytes
Code size:              108 bytes
Data size:               80 bytes
Stack/heap size:      16384 bytes; estimated max. usage=9 cells (36 bytes)
Total requirements:   16688 bytes

Обратите внимание на строку "Data size". Это и есть занятое пространство в секционной памяти (ее еще называют статической). При таком использовании макроса статическая память будет занята на 56 байт. А теперь попробуем поместить текст один раз в статическую память и пользоваться им так, как и раньше. В этом нам поможет оператор static. На локальном уровне (внутри одного файла) именно его содержимое попадает в сегмент данных. Следует учесть, что значения, объявленные при помощи static, попадают в сегмент данных лишь один раз (при компиляции) и в процессе работы сервера не удаляются оттуда. Потому память и статическая - она не изменяет свою наполненность в процессе работы сервера. В итоге, с использованием оператора static, наш текст будет иметь следующий вид:

static TEXT[] = "your text";

В общем виде данный мною ранее участок кода примет такой облик:

static TEXT[] = "your text";
public OnGameModeInit()
{
	printf(TEXT);
	printf(TEXT);
	return 1;
}

Скомпилируем получившийся код. Смотрим снова на информацию, полученную в процессе компилирования.

Header size:            140 bytes
Code size:              112 bytes
Data size:               40 bytes
Stack/heap size:      16384 bytes; estimated max. usage=9 cells (36 bytes)
Total requirements:   16676 bytes

Снова все свое внимание уделяем строке "Data size". Мы, как и раньше, дважды используем один и тот же текст в процессе работы сервера. Но теперь размер статической памяти уменьшился вдвое. Почему? Это как раз и связано с тем, что мы лишь единожды занесли текст в сегмент данных и в дальнейшем работали только с ним.

 

 

Данная ситуация касается не только макросов, но и в целом отправки одинакового текста.

Спойлер

CMD:sendmessage(playerid, params[])
{
	if(isnull(params))
		return SendClientMessage(playerid, -1, "Используйте /sendmessage [playerid] [text]");
	new id;
	if(sscanf(params, "us[100]", id, params))
		return SendClientMessage(playerid, -1, "Используйте /sendmessage [playerid] [text]");
	else if(id == INVALID_PLAYER_ID)
		return SendClientMessage(playerid, -1, "Указанный Вами игрок не найден на сервере.");
	
	SendClientMessage(playerid, -1, "Сообщение успешно отправлено в чат от лица указанного пользователя.");
	OnPlayerText(id, params);
	return 1;
}

 

Заметьте, один и тот же текст ("Используйте /sendmessage [playerid] [text]") дважды используется в одной и той же команде. Это значит, что он дважды будет помещен в сегмент данных. Назревает вопрос: почему бы не уменьшить использованный размер сегмента данных и не занести данный текст лишь единожды? Сделаем это при помощи оператора static.

 
Спойлер

CMD:sendmessage(playerid, params[])
{
	static USE_CMD_MSG[] = "Используйте /sendmessage [playerid] [text]";
	
	if(isnull(params))
		return SendClientMessage(playerid, -1, USE_CMD_MSG);
	new id;
	if(sscanf(params, "us[100]", id, params))
		return SendClientMessage(playerid, -1, USE_CMD_MSG);
	else if(id == INVALID_PLAYER_ID)
		return SendClientMessage(playerid, -1, "Указанный Вами игрок не найден на сервере.");
	
	SendClientMessage(playerid, -1, "Сообщение успешно отправлено в чат от лица указанного пользователя.");
	OnPlayerText(id, params);
	return 1;
}

 

Спойлер
  • Выгоду можно получить лишь тогда, когда текст используется действительно многократно. Например, если у Вас данные для подключения записаны в макрос, выгоду Вы не получите, ведь эти данные используются лишь однажды. 
  • Значение макросов можно "вставить" в строку без форматирования. С переменными (static) такое не получится. Поэтому переводить в данном случае названия сервера, версию сервера с макроса на переменную стоит с умом. Ведь можно получить ошибки, которые не все могут исправить.
  • Static функционирует лишь внутри того или иного файла. Соответственно, значение данного оператора (строку, записанную с помощью static), Вы не сможете использовать во включаемых файлах (includes). Потому в таковом случае придется использовать оператор new. Текст будет также записан в память всего лишь однажды. И, если объявление осуществляется на глобальном уровне, то ничего абсолютно не изменится. Если же через оператор new объявлять какие-либо константы внутри функций, они каждый раз будут инициализироваться при вызове этой функции, что повлияет на скорость.
  • Навряд ли в процессе работы сервера Вы будете изменять данные тексты. Чтобы в будущем не ошибиться и ничего себе не "сломать", рекомендуется ставить возле таких текстов оператор const. Например, " static const SERVER_NAME[] = "Your server's name!"; "
  • Для большей оптимизации и большего сокращения расхода памяти Вы также можете применить упаковку строк. Об упаковке строк урок Вы сможете найти на нашем форуме, воспользовавшись поиском.
Спойлер

Q1: Ведь на строки уходит совсем немного памяти - буквально байты, пусть даже килобайты. Зачем это промышлять?

A1: Любой разработчик должен стремиться к оптимизации. Прежде всего в оптимизацию входит улучшение производительности и уменьшение затраченной памяти. Возможно, не лишним было бы занести в оптимизацию и читаемость кода. Пусть это даже байты, но чем меньше ресурсов затрачено, тем лучше. Более того, для того, чтобы заменить #define на static нужно вовсе немного времени. Останется лишь еще оформить его как строку, а значит указать, что содержит массив (доставить [размер]), добавить присвоение (=), а также доставить на конце строки точку с запятой. Это явно ни час работы, ни полчаса и даже не минута. Однако, не нужно и слишком "загоняться" с этим. Не стоит убивать свое время на трансляцию текста из макросов в static. Лишь стоит иметь в виду в будущем, как лучше задать тот или иной участок кода.

 

Q2: Как получить информацию, предоставленную в данной теме?

A2: Вы можете указать ключ компиляции в конфигурационном файле. Для этого в файле pawn.cfg, находящемуся в папке pawno, необходимо ввести "-v2" без кавычек. При отсутствии данного файла Вам следует его создать.

 

Share this post


Link to post
Share on other sites

Your content will need to be approved by a moderator

Guest
You are commenting as a guest. If you have an account, please sign in.
Reply to this topic...

×   You have pasted content with formatting.   Remove formatting

  Only 75 emoticons maximum are allowed.

×   Your link has been automatically embedded.   Display as a link instead

×   Your previous content has been restored.   Clear editor

Loading...
Sign in to follow this  
Followers 0

  • Recently Browsing   0 members

    No registered users viewing this page.