PDA

Просмотр полной версии : [Function] Чистим массив игрока



Salvacore
26.03.2014, 22:21
Функция

stock memset(aArray[], iValue, iSize = sizeof(aArray)) {
new iAddress;
#emit LOAD.S.pri 12
#emit STOR.S.pri iAddress
iSize *= 4;
while (iSize > 0) {
if (iSize >= 4096) {
#emit LOAD.S.alt iAddress
#emit LOAD.S.pri iValue
#emit FILL 4096
iSize -= 4096;
iAddress += 4096;
} else if (iSize >= 1024) {
#emit LOAD.S.alt iAddress
#emit LOAD.S.pri iValue
#emit FILL 1024
iSize -= 1024;
iAddress += 1024;
} else if (iSize >= 256) {
#emit LOAD.S.alt iAddress
#emit LOAD.S.pri iValue
#emit FILL 256
iSize -= 256;
iAddress += 256;
} else if (iSize >= 64) {
#emit LOAD.S.alt iAddress
#emit LOAD.S.pri iValue
#emit FILL 64
iSize -= 64;
iAddress += 64;
} else if (iSize >= 16) {
#emit LOAD.S.alt iAddress
#emit LOAD.S.pri iValue
#emit FILL 16
iSize -= 16;
iAddress += 16;
} else {
#emit LOAD.S.alt iAddress
#emit LOAD.S.pri iValue
#emit FILL 4
iSize -= 4;
iAddress += 4;
}
}
#pragma unused aArray
}
Использование

enum _:test {
ID,
Float:POS,
NAME[MAX_PLAYER_NAME]
}
new PLAYER[MAX_PLAYERS][test];
public OnPlayerDisconnect(playerid) {
memset(PLAYER[playerid],0, _:test);
return true;
}

Автор: Slice

Profyan
26.12.2015, 11:28
Т.е за место того, чтобы 100500 строк писать(PI[playerid][Variable] =0,PI[playerid][Variable1] =0,PI[playerid][Variable2] =0...), можно использовать ее!?Насколько безопасна и эффективна эта функция?

Daniel_Cortez
26.12.2015, 15:38
Т.е за место того, чтобы 100500 строк писать(PI[playerid][Variable] =0,PI[playerid][Variable1] =0,PI[playerid][Variable2] =0...), можно использовать ее!?Насколько безопасна и эффективна эта функция?
Судя по коду, выход за предел буфера будет только если указать неправильный размер.

Функция работает следующим образом: в наборе инструкций AMX есть опкод "fill", который заполняет блок данных, указанный в регистре alt значением из регистра pri. Этот опкод работает очень быстро, но в нём нельзя задать размер блока динамически, только в виде константы (динамически можно только указать через регистры адрес и значение). Поэтому в данной реализации memset массив разделяется на блоки по 4096, 1024, 256, 64, 16 и 4 байта, каждый из блоков заполняется своей инструкцией fill.

По поводу оптимальности: запись в ячейки массива с помощью обычного цикла работает куда медленнее. Даже если оптимизировать такой цикл с помощью #emit и в цикле вместо счётчика использовать адрес текущей ячейки, такой цикл будет выигрывать только на небольших массивах (до 31 ячейки).
Можете взять профайлер (http://pro-pawn.ru/showthread.php?12585) и проверить моё утверждение (регулируйте размер массива в PROFILER_ARRAY_SIZE):


/*Настройки.*/
const PROFILER_ITERATIONS_MAJOR = 1_000_000;
const PROFILER_ITERATIONS_MINOR = 10;

new const code_snippets_names[2][] =
{
{"memset by Slice"},
{"memset by Daniel_Cortez"}
};


const PROFILER_ARRAY_SIZE = 31;

stock memset(aArray[], iValue, iSize = sizeof(aArray)) {
new iAddress;
#emit LOAD.S.pri 12
#emit STOR.S.pri iAddress
iSize *= 4;
while (iSize > 0) {
if (iSize >= 4096) {
#emit LOAD.S.alt iAddress
#emit LOAD.S.pri iValue
#emit FILL 4096
iSize -= 4096;
iAddress += 4096;
} else if (iSize >= 1024) {
#emit LOAD.S.alt iAddress
#emit LOAD.S.pri iValue
#emit FILL 1024
iSize -= 1024;
iAddress += 1024;
} else if (iSize >= 256) {
#emit LOAD.S.alt iAddress
#emit LOAD.S.pri iValue
#emit FILL 256
iSize -= 256;
iAddress += 256;
} else if (iSize >= 64) {
#emit LOAD.S.alt iAddress
#emit LOAD.S.pri iValue
#emit FILL 64
iSize -= 64;
iAddress += 64;
} else if (iSize >= 16) {
#emit LOAD.S.alt iAddress
#emit LOAD.S.pri iValue
#emit FILL 16
iSize -= 16;
iAddress += 16;
} else {
#emit LOAD.S.alt iAddress
#emit LOAD.S.pri iValue
#emit FILL 4
iSize -= 4;
iAddress += 4;
}
}
#pragma unused aArray
}

memset2(array[], value, size = sizeof(array))
{
#pragma unused array
static addr, end_addr;
const cell_size = cellbits / charbits;
#emit load.s.alt array
#emit stor.alt addr
#emit load.s.pri size
#emit smul.c cell_size
#emit add
#emit stor.pri end_addr
do
{
#emit load.alt addr
#emit load.s.pri value
#emit stor.i
}
while((addr += cell_size) != end_addr);
}


#define Prerequisites();\
new array[PROFILER_ARRAY_SIZE];

#define CodeSnippet1();\
memset(array, 0);

#define CodeSnippet2();\
memset2(array, 0);
/*Конец настроек.*/

Реализация от Slice проигрывает на массивах менее 32 элементов, но всё равно выигрывает на массивах по 24 (16 + 8), 16, 12 (8 + 4) и 9 ячеек, т.к. его алгоритм оптимизирован под блоки размером со степени двойки.

Profyan
26.12.2015, 15:54
Судя по коду, выход за предел буфера будет только если указать неправильный размер.

Функция работает следующим образом: в наборе инструкций AMX есть опкод "fill", который заполняет блок данных, указанный в регистре alt значением из регистра pri. Этот опкод работает очень быстро, но в нём нельзя задать размер блока динамически, только в виде константы (динамически можно только указать через регистры адрес и значение). Поэтому в данной реализации memset массив разделяется на блоки по 4096, 1024, 256, 64, 16 и 4 байта, каждый из блоков заполняется своей инструкцией fill.

По поводу оптимальности: запись в ячейки массива с помощью обычного цикла работает куда медленнее. Даже если оптимизировать такой цикл с помощью #emit и в цикле вместо счётчика использовать адрес текущей ячейки, такой цикл будет выигрывать только на небольших массивах (до 31 ячейки).
Можете взять профайлер (http://pro-pawn.ru/showthread.php?12585) и проверить моё утверждение (регулируйте размер массива в PROFILER_ARRAY_SIZE):


/*Настройки.*/
const PROFILER_ITERATIONS_MAJOR = 1_000_000;
const PROFILER_ITERATIONS_MINOR = 10;

new const code_snippets_names[2][] =
{
{"memset by Slice"},
{"memset by Daniel_Cortez"}
};


const PROFILER_ARRAY_SIZE = 31;

stock memset(aArray[], iValue, iSize = sizeof(aArray)) {
new iAddress;
#emit LOAD.S.pri 12
#emit STOR.S.pri iAddress
iSize *= 4;
while (iSize > 0) {
if (iSize >= 4096) {
#emit LOAD.S.alt iAddress
#emit LOAD.S.pri iValue
#emit FILL 4096
iSize -= 4096;
iAddress += 4096;
} else if (iSize >= 1024) {
#emit LOAD.S.alt iAddress
#emit LOAD.S.pri iValue
#emit FILL 1024
iSize -= 1024;
iAddress += 1024;
} else if (iSize >= 256) {
#emit LOAD.S.alt iAddress
#emit LOAD.S.pri iValue
#emit FILL 256
iSize -= 256;
iAddress += 256;
} else if (iSize >= 64) {
#emit LOAD.S.alt iAddress
#emit LOAD.S.pri iValue
#emit FILL 64
iSize -= 64;
iAddress += 64;
} else if (iSize >= 16) {
#emit LOAD.S.alt iAddress
#emit LOAD.S.pri iValue
#emit FILL 16
iSize -= 16;
iAddress += 16;
} else {
#emit LOAD.S.alt iAddress
#emit LOAD.S.pri iValue
#emit FILL 4
iSize -= 4;
iAddress += 4;
}
}
#pragma unused aArray
}

memset2(array[], value, size = sizeof(array))
{
#pragma unused array
static addr, end_addr;
const cell_size = cellbits / charbits;
#emit load.s.alt array
#emit stor.alt addr
#emit load.s.pri size
#emit smul.c cell_size
#emit add
#emit stor.pri end_addr
do
{
#emit load.alt addr
#emit load.s.pri value
#emit stor.i
}
while((addr += cell_size) != end_addr);
}


#define Prerequisites();\
new array[PROFILER_ARRAY_SIZE];

#define CodeSnippet1();\
memset(array, 0);

#define CodeSnippet2();\
memset2(array, 0);
/*Конец настроек.*/

Реализация от Slice проигрывает на массивах менее 32 элементов, но всё равно выигрывает на массивах по 24 (16 + 8), 16, 12 (8 + 4) и 9 ячеек, т.к. его алгоритм оптимизирован под блоки размером со степени двойки.

Спасибо за разъяснение.Будет время - протестирую.

Mopok
26.12.2015, 21:49
:scratch_one-s_head: Из выше разложенного непонятного для восприятия ламерским мозгом разъяснения по поводу данной функции, а также учитывая непонятое соответствие названия темы с тем же разъяснением, можно ли сделать вывод в двух словах, что данная функция ускоряет запись определённой переменной определённого массива и использует меньше памяти?:mamba:

Profyan
27.12.2015, 09:12
:scratch_one-s_head: Из выше разложенного непонятного для восприятия ламерским мозгом разъяснения по поводу данной функции, а также учитывая непонятое соответствие названия темы с тем же разъяснением, можно ли сделать вывод в двух словах, что данная функция ускоряет запись определённой переменной определённого массива и использует меньше памяти?:mamba:


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

Mopok
27.12.2015, 11:51
О, спасибо, что бы я без умников делал, наверно никогда не читал бы внимательней. Нашёл ответ на англоязычном форуме, здесь полагаю если хочешь понять, нужно априори уже разбираться и понимать, также что именно она там чистит это инфа для прошаренных. Претензий не имею. Спс. :crazy: :butthurt:

Mopok
27.12.2015, 15:08
Эффективнее ли использовать функцию в присвоении не всему массиву определённого значения, а одной ячейке в отдельности?

Daniel_Cortez
27.12.2015, 22:26
Эффективнее ли использовать функцию в присвоении не всему массиву определённого значения, а одной ячейке в отдельности?
Зачем использовать функцию для записи всего в одну ячейку, когда можно записать в неё значение напрямую?
Если вы не понимаете разницы, то лучше не задумывайтесь о подобных оптимизациях, не то наделаете чего-нибудь, не разобравшись.

TheMallard
29.12.2015, 14:38
for(new pInfo:e; e < pInfo; ++e) PlayerInfo[playerid][e] = 0;

Роуди.
10.01.2016, 20:55
for(new pInfo:e; e < pInfo; ++e) PlayerInfo[playerid][e] = 0;

Выпрыгни с окна, пожалуйста

L0ndl3m
10.01.2016, 21:04
Выпрыгни с окна, пожалуйста
Может вы предложите лучший вариант, господин?

ziggi
10.01.2016, 21:15
Может вы предложите лучший вариант, господин?

Можно создать глобальный массив:


new NULL_PlayerInfo[pInfo];


И потом использовать таким образом:


PlayerInfo[playerid] = NULL_PlayerInfo;

Роуди.
11.01.2016, 18:43
Может вы предложите лучший вариант, господин?

тот, который предложил Т/С =\

123
16.08.2016, 10:51
Можно создать глобальный массив:


new NULL_PlayerInfo[pInfo];


И потом использовать таким образом:


PlayerInfo[playerid] = NULL_PlayerInfo;


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

Geebrox
20.10.2016, 03:20
Можно создать глобальный массив:


new NULL_PlayerInfo[pInfo];


И потом использовать таким образом:


PlayerInfo[playerid] = NULL_PlayerInfo;


строки не чистятся

vovandolg
20.10.2016, 12:33
строки не чистятся

Хм странно, у меня всё там чистится(кхм точнее тут не очистка, а замена на нулевые переменные которые были заданы при создании)...

DeimoS
20.10.2016, 13:14
Хм странно, у меня всё там чистится(кхм точнее тут не очистка, а замена на нулевые переменные которые были заданы при создании)...

Ну так а в случае с функцией, что запостил Salvacore, чистится вообще весь массив

ziggi
20.10.2016, 14:48
строки не чистятся

Чистятся.


Ну так а в случае с функцией, что запостил Saibot, чистится вообще весь массив

Всё-таки чистить массив без циклов в одну строку куда приятнее и быстрее. Более того, есть возможность установки значений по умолчанию, а не только 0:

enum pInfo {
pName[MAX_PLAYER_NAME + 1],
Float:pHealth,
pMoney,
}

new PlayerInfo[MAX_PLAYERS][pInfo];
new NULL_PlayerInfo[pInfo] = {"noname", 50.0, 100};
значения по умолчанию для ID 0:

PlayerInfo[0] = NULL_PlayerInfo;

Geebrox
20.10.2016, 16:01
Хм странно, у меня всё там чистится(кхм точнее тут не очистка, а замена на нулевые переменные которые были заданы при создании)...

Я имел ввиду СТРОК, у меня не чистился, но уже получил ответ от ziggi

vovandolg
21.10.2016, 16:06
Я имел ввиду СТРОК, у меня не чистился, но уже получил ответ от ziggi

У тебя наверняка где то запись повторная шла в массив после очистки
(так как я в логи выводил и массивы пустые были после такой очистки)

Geebrox
22.10.2016, 12:23
У тебя наверняка где то запись повторная шла в массив после очистки
(так как я в логи выводил и массивы пустые были после такой очистки)

может быть, я сейчас заново протестил, строка очистился

DeimoS
22.10.2016, 12:37
Сначала не вник в предложенный вариант и решил, что тут идёт именно очищение нулевой строки (не заметил, что второй массив так же ссылается на enum).

Действительно, отчищаться будет.
Если кто-то не понимает почему, то тут всё просто. Отчищается он из-за того, что мы объявляем второй массив с тем же числом ячеек, при этом не заполнив его никакими значениями (следовательно, он заполнится нулями), а после приравниваем первый массив ко второму, из-за чего первый "перенимает" все значения второго (использование оператора "=" прокатит только в том случае, когда массивы либо одинаковые по размеру, либо второй массив меньше). Так как второй массив заполнен нулями, первый так же "обнулится".

Вот простой пример с локальным массивом

new string[] = "Pro-Pawn";
static const NULL_string[sizeof(string)];
print(string);
string = NULL_string;
print(string);
print(string[3]);

Я бы и

new NULL_PlayerInfo[pInfo] = {"noname", 50.0, 100};
предложил оформить как константу, дабы "случайно" не изменить в нём какие-либо данные

ziggi
22.11.2018, 10:17
Заметил, что способ с NULL_ может вызывать проблемы, связанные с перезаписью лишних ячеек памяти.

Имелась такая структура массивов:

enum PlayerInterfaceParams {
PIP_TextDraw,
PIP_Visible,
}

enum PlayerInterface {
PI_Armour,
PI_Health,
PI_Info,
PI_LevelLineBackground,
PI_LevelLine,
PI_LevelLevel,
PI_LevelPlus,
PI_LevelXP,
PI_LevelXPPlus,
PI_MoneyBorder,
PI_MoneyBackground,
PI_MoneyMoney,
PI_MoneyPlus,
PI_WeaponSkill,
}

new
gPlayerInterface[MAX_PLAYERS][PlayerInterface][PlayerInterfaceParams],
NULL_gPlayerInterface[PlayerInterface][PlayerInterfaceParams];

Использовал я так:

public OnPlayerConnect(playerid)
{
gPlayerInterface[playerid] = NULL_gPlayerInterface;
TogglePlayerInterfaceVisibility(playerid, true, true);
return 1;
}
И с таким кодом происходили разного рода проблемы, значения менялись и у другого игрока, например.

А с этим кодом всё работает нормально:

public OnPlayerConnect(playerid)
{
for (new i = 0; i < sizeof(gPlayerInterface[]); i++) {
for (new j = 0; j < sizeof(gPlayerInterface[][]); j++) {
gPlayerInterface[playerid][PlayerInterface:i][PlayerInterfaceParams:j] = 0;
}
}
TogglePlayerInterfaceVisibility(playerid, true, true);
return 1;
}

Так что вариант с NULL_ небезопасный, лучше его не использовать.

Daniel_Cortez
23.11.2018, 20:26
Похоже на баг компилятора. Я взял код из поста выше, закомментировал все поля в PlayerInterface кроме одного PI_Armour (чтобы было проще смотреть на отладочный вывод) и добавил код для инициализации ячеек массива gPlayerInterface и отладочного вывода значений из него.


enum PlayerInterfaceParams
{
PIP_TextDraw,
PIP_Visible,
};
enum PlayerInterface
{
PI_Armour,
/*
PI_Health,
PI_Info,
PI_LevelLineBackground,
PI_LevelLine,
PI_LevelLevel,
PI_LevelPlus,
PI_LevelXP,
PI_LevelXPPlus,
PI_MoneyBorder,
PI_MoneyBackground,
PI_MoneyMoney,
PI_MoneyPlus,
PI_WeaponSkill,
*/
};
new gPlayerInterface[MAX_PLAYERS][PlayerInterface][PlayerInterfaceParams];
new NULL_gPlayerInterface[PlayerInterface][PlayerInterfaceParams];

InitPlayerInterface(playerid, value)
{
for (new i = 0; i < _:PlayerInterface; ++i)
for (new j = 0; j < _:PlayerInterfaceParams; ++j)
gPlayerInterface[playerid][PlayerInterface:i][PlayerInterfaceParams:j] = value;
}

DebugPrintPlayerInterface(playerid)
{
for (new i = 0; i < _:PlayerInterface; ++i)
for (new j = 0; j < _:PlayerInterfaceParams; ++j)
printf(
"\tgPlayerInterface[%d][%d][%d]: 0x%08x",playerid, i, j,
gPlayerInterface[playerid][PlayerInterface:i][PlayerInterfaceParams:j]);
}

main()
{
InitPlayerInterface(0, 0xCCCCCCCC);
InitPlayerInterface(1, 0xDDDDDDDD);
InitPlayerInterface(2, 0xEEEEEEEE);
InitPlayerInterface(3, 0xFFFFFFFF);
printf("Before:");
for (new i = 0; i < 4; ++i)
DebugPrintPlayerInterface(i);
new playerid = 0;
gPlayerInterface[playerid] = NULL_gPlayerInterface;
printf("After:");
for (new i = 0; i < 4; ++i)
DebugPrintPlayerInterface(i);
}


Вывод:



Before:
gPlayerInterface[0][0][0]: 0xCCCCCCCC
gPlayerInterface[0][0][1]: 0xCCCCCCCC
gPlayerInterface[1][0][0]: 0xDDDDDDDD
gPlayerInterface[1][0][1]: 0xDDDDDDDD
gPlayerInterface[2][0][0]: 0xEEEEEEEE
gPlayerInterface[2][0][1]: 0xEEEEEEEE
gPlayerInterface[3][0][0]: 0xFFFFFFFF
gPlayerInterface[3][0][1]: 0xFFFFFFFF
After:
gPlayerInterface[0][0][0]: 0x00000000
gPlayerInterface[0][0][1]: 0x00000000
gPlayerInterface[1][0][0]: 0x00000000
gPlayerInterface[1][0][1]: 0x00000000
gPlayerInterface[2][0][0]: 0x00000000
gPlayerInterface[2][0][1]: 0x00000FAC
gPlayerInterface[3][0][0]: 0xFFFFFFFF
gPlayerInterface[3][0][1]: 0xFFFFFFFF


Как и было сказано, обнуляются ячейки не только для playerid, но и для следующих двух игроков. Что интересно, в gPlayerInterface[2][0][1] вместо 0 было записано значение 0x00000FAC - скорее всего, оно попало туда по ошибке из таблицы смещений от массива gPlayerInterface.

@ziggi, я сообщу об этом баге в репо компилятора, или ты сам?

ziggi
23.11.2018, 22:30
@ziggi, я сообщу об этом баге в репо компилятора, или ты сам?

Сообщи, у тебя лучше получится объяснить)

Daniel_Cortez
24.11.2018, 14:49
Сообщи, у тебя лучше получится объяснить)
Ок, https://github.com/pawn-lang/compiler/issues/388

Забыл сказать, проблема весьма специфическая и проявляется только когда в обоих массивах (т.е. и в массиве-источнике, и в приёмнике) есть 2 и более enum-измерения. Иными словами, если убрать у массивов последнее измерение

new gPlayerInterface[MAX_PLAYERS][PlayerInterface];
new const NULL_gPlayerInterface[PlayerInterface];

копирование будет происходить правильно, без перезаписи лишних ячеек.

bullplex
27.01.2019, 21:09
Ок, https://github.com/pawn-lang/compiler/issues/388

Забыл сказать, проблема весьма специфическая и проявляется только когда в обоих массивах (т.е. и в массиве-источнике, и в приёмнике) есть 2 и более enum-измерения. Иными словами, если убрать у массивов последнее измерение

new gPlayerInterface[MAX_PLAYERS][PlayerInterface];
new const NULL_gPlayerInterface[PlayerInterface];

копирование будет происходить правильно, без перезаписи лишних ячеек.

При const невозможно использование (вылетают ошибки 017 и 033). Пример: gPlayerInterface[playerid] = NULL_gPlayerInterface;

Daniel_Cortez
01.02.2019, 18:05
При const невозможно использование (вылетают ошибки 017 и 033). Пример: gPlayerInterface[playerid] = NULL_gPlayerInterface;
Ищите причину в своём коде, чистый скрипт компилируется без проблем.
http://ihost.pro-pawn.ru/image.php?di=BWLZ