PDA

Просмотр полной версии : [Вопрос] Использование памяти



punkochel
16.06.2019, 21:19
Доброго времени суток. В теме я хочу задать 2 похожих вопроса о правильном использовании памяти.

1) Почему в перехватываемой функции значительно увеличивается размер стека?

Приведу ситуацию:

В этом случае подсчет стека идет понятно как, и тут вроде бы все правильно.

https://i.imgur.com/eL4LwIe.png

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

https://i.imgur.com/Q9iJxPm.png

Почему произошло увеличение размера стека?

Код инклуда который я подключил:

public OnDialogResponse(playerid, dialogid, response, listitem, inputtext[])
{
switch(dialogid)
{
case DLG_TEST3:
{
new dialog[100];
format(dialog, sizeof(dialog), "%i %i %i", variable1, variable2, variable3);
}
case DLG_TEST4:
{
new dialog[100];
format(dialog, sizeof(dialog), "%i %i %i", variable1, variable2, variable3);
}
}
#if defined test__OnDialogResponse
return test__OnDialogResponse(playerid, dialogid, response, listitem, inputtext);
#endif
}
#if defined _ALS_OnDialogResponse
#undef OnDialogResponse
#else
#define _ALS_OnDialogResponse
#endif
#define OnDialogResponse test__OnDialogResponse
#if defined test__OnDialogResponse
forward test__OnDialogResponse(playerid, dialogid, response, listitem, inputtext[]);
#endif

2) Стоит ли в инклудах, где используется перехват функций использовать переменные static для создания массива?

Как один из вариантов решения который вроде-бы более-менее практичный (на мой взгляд):

case DLG_TEST3:
{
static dialog[100];
format(dialog, sizeof(dialog), "%i %i %i", variable1, variable2, variable3);
}
case DLG_TEST4:
{
static dialog[100];
format(dialog, sizeof(dialog), "%i %i %i", variable1, variable2, variable3);
}

Или-же вывести static dialog[100] в общий блок колбека.

DeimoS
16.06.2019, 23:19
1) Потому что перехват вызывает перехватываемую функцию, а не просто волшебным образом совмещает код так, как бы это было при написании кода в одном паблике.

2) Нет. Этим ты перенесёшь массив из стека в глобальный сегмент данных, начав использовать новую память, а не уже выделенный стек. Если нет нужды использовать одну и ту же информацию, которую записываешь в переменные, в нескольких пабликах/функциях, всегда используй локальные переменные и, в случае нужды, увеличивай стек, а не плоди глобальные переменные с целью экономии стека. Вот тут (http://pro-pawn.ru/showthread.php?16533-%D0%92%D0%BE%D0%BF%D1%80%D0%BE%D1%81-%D0%BF%D0%BE-Stack-heap-size&p=92930&viewfull=1#post92930) я подробно рассказывал об этом.

Daniel_Cortez
17.06.2019, 13:49
Как вариант, можно ограничить код перехвата локальным блоком, чтобы локальные переменные уничтожались до вызова перехватываемой функции:

public OnDialogResponse(playerid, dialogid, response, listitem, inputtext[])
{
{
// Локальные переменные создавать здесь
}
// Здесь локальные переменные уже перестанут существовать, высвободив место в стеке
#if defined test__OnDialogResponse
return test__OnDialogResponse(playerid, dialogid, response, listitem, inputtext);
#endif
}

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

punkochel
17.06.2019, 22:17
Вариант с помещением кода в блок до вызова перехватываемой функции я пробовал, результат остался прежним, после чего я сразу побежал снова искать вашу тему о перехватах функции, где вы указали на подобный принцип экономии памяти.
Этим я хотел сделать архитектуру проекта, где-бы каждая система была помещена в отдельный файл. Может быть у вас есть какие-нибудь предложения, как можно сделать подобное?
Создам другую тему для решения вопроса.
Спасибо за ответ

DeimoS
17.06.2019, 22:52
Так а что не так? Если напрягает потребление памяти, то компилятор просто рассчитывает отдельно размер под вызываемые функции и отдельно под массивы. То бишь, у тебя не все функции будут складываться подобным образом, потребляя стек, а из них будет определена одна, которая потребляет больше всего стека, и учитываться будет её размер.

То бишь, этот код, грубо говоря, будет потреблять 1000 ячеек, а не 1200.

main()
{
new string[500];
SomeFunc1();
SomeFunc2();
}

stock SomeFunc1()
{
new string[500];
}

stock SomeFunc2()
{
new string[200];
}

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

Daniel_Cortez
18.06.2019, 13:27
То бишь, этот код, грубо говоря, будет потреблять 1000 ячеек, а не 1200.

main()
{
new string[500];
SomeFunc1();
SomeFunc2();
}

stock SomeFunc1()
{
new string[500];
}

stock SomeFunc2()
{
new string[200];
}
Не совсем корректный пример, компилятор и так выдаёт в нём 1000 ячеек. Соль в том, что компилятор подсчитывает использование стека массивами так, как будто все массивы в цепочке вызова функций существуют одновременно, даже если это не так.
Вот упрощённый пример:

stock SomeFunc()
{
new arr2[500];
}

main()
{
SomeFunc();
new arr1[500];
}

На деле массивы arr1 и arr2 существуют по отдельности и использование стека не превышает 500 ячеек, но компилятор в отчёте выдаёт все 1000 (на самом деле 1007, т.к. ещё 7 ячеек используются для вызова функций main и SomeFunc).

DeimoS
18.06.2019, 14:19
Не совсем корректный пример, компилятор и так выдаёт в нём 1000 ячеек. Соль в том, что компилятор подсчитывает использование стека массивами так, как будто все массивы в цепочке вызова функций существуют одновременно, даже если это не так.
Вот упрощённый пример:

stock SomeFunc()
{
new arr2[500];
}

main()
{
SomeFunc();
new arr1[500];
}

На деле массивы arr1 и arr2 существуют по отдельности и использование стека не превышает 500 ячеек, но компилятор в отчёте выдаёт все 1000 (на самом деле 1007, т.к. ещё 7 ячеек используются для вызова функций main и SomeFunc).

Ты просто не понял суть моего примера :)
Я отвечал на возможные опасения автора о том, что теперь каждая подключаемая функция будет потреблять стек, хотя на деле будет учитываться только одна функция, которая потребляет больше всего + переменные из паблика, а не все вызываемые функции + переменные из паблика.
Я, собственно, то же самое пытался передать, что и ты, просто ещё добавил объяснение про вызов дополнительных функций.

punkochel
18.06.2019, 15:10
Получается что на практике стек будет иметь размер, подобный тому если мы просто "волшебным образом" добавим код из файла в мод?

DeimoS
18.06.2019, 15:58
Получается что на практике стек будет иметь размер, подобный тому если мы просто "волшебным образом" добавим код из файла в мод?

Нет. Если бы код из функций переместили в паблик, то и размер стека рассчитывался бы для всего кода целиком...
В общем, постараюсь показать на примерах:
main()
{
{
new array1[500];
#pragma unused array1
}
{
new array2[500];
#pragma unused array2
}

/*
Хоть вместе размер двух массивов сверху будет равен 1000,
на деле под массивы будет выделено всего 500 ячеек стека, так как
оба находятся в отдельных блоках кода и каждый раз, когда
обработка конкретного блока кода будет завершена, массив будет удаляться из
стека и занимаемое им место станет доступно для другого массива.
*/


/*
С функциями работает практически тот же принцип (одна функция - один отдельный блок кода).
И, в итоге, при вычислении потребляемого стека, компилятор будет ориентироваться на функцию,
которая потребляет больше всего стека, а не будет складывать размер потребляемого стека всех функций.
То бишь, функция начала обрабатываться, создала все свои локальные переменные и когда обработка закончилась,
все переменные из памяти удаляются. Поэтому компилятор учитывает размер самой "тяжёлой" функции, так как в таком
случае этого размера хватит для всех остальных функций. В общем, тот же самый принцип, что и при рассчёте примера выше.
*/
SomeFunc1();
SomeFunc2();

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

То бишь, при расчёте размера стека отдельно учитывается размер для переменных и отдельно размер для функций,
а после они складываются.
*/
}

stock SomeFunc1()
{
new array[500];
#pragma unused array
}

stock SomeFunc2()
{
new array[200];
#pragma unused array
}

В этом случае стек будет равен 1004 ячейкам. Если же просто вставить код функций напрямую, поместив его в отдельные блоки:
main()
{
{
new array1[500];
#pragma unused array1
}
{
new array2[500];
#pragma unused array2
}

{
new array[500];
#pragma unused array
}
{
new array[500];
#pragma unused array
}
}

То размер стека станет равным 503 ячейкам. Собственно, если бы не баг компилятора, то и в первом случае он был бы равным 503 ячейкам. Поэтому вставка кода напрямую != вызов функции. По крайней мере пока не исправят этот баг с компилятором.

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