Просмотр полной версии : [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
Т.е за место того, чтобы 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 ячеек, т.к. его алгоритм оптимизирован под блоки размером со степени двойки.
Судя по коду, выход за предел буфера будет только если указать неправильный размер.
Функция работает следующим образом: в наборе инструкций 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 ячеек, т.к. его алгоритм оптимизирован под блоки размером со степени двойки.
Спасибо за разъяснение.Будет время - протестирую.
:scratch_one-s_head: Из выше разложенного непонятного для восприятия ламерским мозгом разъяснения по поводу данной функции, а также учитывая непонятое соответствие названия темы с тем же разъяснением, можно ли сделать вывод в двух словах, что данная функция ускоряет запись определённой переменной определённого массива и использует меньше памяти?:mamba:
:scratch_one-s_head: Из выше разложенного непонятного для восприятия ламерским мозгом разъяснения по поводу данной функции, а также учитывая непонятое соответствие названия темы с тем же разъяснением, можно ли сделать вывод в двух словах, что данная функция ускоряет запись определённой переменной определённого массива и использует меньше памяти?:mamba:
По поводу оптимальности: запись в ячейки массива с помощью обычного цикла работает куда медленнее. Даже если оптимизировать такой цикл с помощью #emit и в цикле вместо счётчика использовать адрес текущей ячейки, такой цикл будет выигрывать только на небольших массивах (до 31 ячейки).
Внимательней читайте.
О, спасибо, что бы я без умников делал, наверно никогда не читал бы внимательней. Нашёл ответ на англоязычном форуме, здесь полагаю если хочешь понять, нужно априори уже разбираться и понимать, также что именно она там чистит это инфа для прошаренных. Претензий не имею. Спс. :crazy: :butthurt:
Эффективнее ли использовать функцию в присвоении не всему массиву определённого значения, а одной ячейке в отдельности?
Daniel_Cortez
27.12.2015, 22:26
Эффективнее ли использовать функцию в присвоении не всему массиву определённого значения, а одной ячейке в отдельности?
Зачем использовать функцию для записи всего в одну ячейку, когда можно записать в неё значение напрямую?
Если вы не понимаете разницы, то лучше не задумывайтесь о подобных оптимизациях, не то наделаете чего-нибудь, не разобравшись.
TheMallard
29.12.2015, 14:38
for(new pInfo:e; e < pInfo; ++e) PlayerInfo[playerid][e] = 0;
for(new pInfo:e; e < pInfo; ++e) PlayerInfo[playerid][e] = 0;
Выпрыгни с окна, пожалуйста
Выпрыгни с окна, пожалуйста
Может вы предложите лучший вариант, господин?
Может вы предложите лучший вариант, господин?
Можно создать глобальный массив:
new NULL_PlayerInfo[pInfo];
И потом использовать таким образом:
PlayerInfo[playerid] = NULL_PlayerInfo;
Может вы предложите лучший вариант, господин?
тот, который предложил Т/С =\
Можно создать глобальный массив:
new NULL_PlayerInfo[pInfo];
И потом использовать таким образом:
PlayerInfo[playerid] = NULL_PlayerInfo;
Протестил все варианты предложенные в этой теме, этот самый быстрый, при этом на огромном отрыве) Полезно, если очищать нужно только один массив.
Можно создать глобальный массив:
new NULL_PlayerInfo[pInfo];
И потом использовать таким образом:
PlayerInfo[playerid] = NULL_PlayerInfo;
строки не чистятся
vovandolg
20.10.2016, 12:33
строки не чистятся
Хм странно, у меня всё там чистится(кхм точнее тут не очистка, а замена на нулевые переменные которые были заданы при создании)...
Хм странно, у меня всё там чистится(кхм точнее тут не очистка, а замена на нулевые переменные которые были заданы при создании)...
Ну так а в случае с функцией, что запостил Salvacore, чистится вообще весь массив
строки не чистятся
Чистятся.
Ну так а в случае с функцией, что запостил 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;
Хм странно, у меня всё там чистится(кхм точнее тут не очистка, а замена на нулевые переменные которые были заданы при создании)...
Я имел ввиду СТРОК, у меня не чистился, но уже получил ответ от ziggi
vovandolg
21.10.2016, 16:06
Я имел ввиду СТРОК, у меня не чистился, но уже получил ответ от ziggi
У тебя наверняка где то запись повторная шла в массив после очистки
(так как я в логи выводил и массивы пустые были после такой очистки)
У тебя наверняка где то запись повторная шла в массив после очистки
(так как я в логи выводил и массивы пустые были после такой очистки)
может быть, я сейчас заново протестил, строка очистился
Сначала не вник в предложенный вариант и решил, что тут идёт именно очищение нулевой строки (не заметил, что второй массив так же ссылается на 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};
предложил оформить как константу, дабы "случайно" не изменить в нём какие-либо данные
Заметил, что способ с 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, я сообщу об этом баге в репо компилятора, или ты сам?
Сообщи, у тебя лучше получится объяснить)
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
Powered by vBulletin® Version 4.2.0 Copyright © 2024 vBulletin Solutions, Inc. All rights reserved. Перевод: zCarot