LINUX.ORG.RU

Луа5.1 и параноидальная оптимизация

 


1

3
local sub, gsub, random = string.sub, string.gsub, math.random
local SendAddonMessage = SendAddonMessage

-- Таблица смещений
local STR_OFFSETS = {}
for i = 1, 100 do
    STR_OFFSETS[i] = (i-1)*3 + 1
end

function time100_Server(channel, text, sender, prefix)
    if prefix ~= "time100" then return end

    local objFull = mFldS:getStaticStr(sender, 1) or ""
    local hpFull = mFldS:getStaticStr(sender, 2) or ""

    local rezT, rezF = {}, {}
    local obj_char, hp_val, formattedHp

    for i = 1, 100 do
        local offset = STR_OFFSETS[i]
        if offset + 2 > #objFull then break end

        obj_char = sub(objFull, offset, offset + 2)
        hp_val = en10(sub(hpFull, offset, offset + 2) or "000")

        if obj_char == "00f" then
            if hp_val < 999 then
                formattedHp = sub("   "..en85(hp_val + 1), -3)
                rezF[#rezF+1] = formattedHp
                mFldS:addStaticStr(sender, 2, i, formattedHp)
            else
                mFldS:addStaticStr(sender, 1, i, "00t")
                mFldS:addStaticStr(sender, 2, i, en85(999))
                rezT[#rezT+1] = "999"
            end
        elseif obj_char == "00t" then
            if hp_val < 999 then
                hp_val = hp_val + 1
            elseif hp_val >= 999 and random(10) == 1 then
                hp_val = hp_val + 1
            end
            formattedHp = sub("   "..en85(hp_val), -3)
            rezT[#rezT+1] = formattedHp
            mFldS:addStaticStr(sender, 2, i, formattedHp)
        end
    end

    if text == "1" then
        local function sendChunked(base, data)
            if #data == 0 then return end
            data = gsub(data, " ", "%0")
            --SendAddonMessage(base.." "..sender, sub(data, 1, 150), "GUILD")
            if #data > 150 then
                --SendAddonMessage(base.."_2 "..sender, sub(data, 151), "GUILD")
            end
        end
        sendChunked("time100_00t", table.concat(rezT))
        sendChunked("time100_00f", table.concat(rezF))
    end
end

Допустим есть такой вот код на луа. Тут мы получаем строку с «объектами». Строка всегда 300 символов, каждый объект всегда 3 символа. Если находим два нужных объекта, итерируем им «здоровье». Записываем изменения в базу и при необходимости отправляем об этом общее уведомление в эфир.

Чтение и изменение строки происходит самописными методами:

-- Добавление или обновление строки
function NsDb:addStaticStr(nik, nStr, nArg, message)
    self.input_table[nik] = self.input_table[nik] or {}

    if not nArg then
        self.input_table[nik][nStr] = message
        return
    end

    -- Обновляем строку
    local currentStr = self.input_table[nik][nStr]
    self.input_table[nik][nStr] = currentStr:sub(1, (nArg - 1) * 3)
                                .. ("   " .. message):sub(-3)
                                .. currentStr:sub(nArg * 3 + 1)
end

function NsDb:getStaticStr(nik, nStr, nArg)
    local str = self.input_table 
              and self.input_table[nik] 
              and self.input_table[nik][nStr]
    
    return str and (nArg and str_sub(str, (nArg-1)*3 + 1, nArg*3) or str)
end

И вот на 10000 проходов задержка где то на секунду и отжор памяти метров на 15.

Избавляться от локальных переменных пробовал - получается криво. Попробовал вон перейти вместо прямого саба и конкатенации на регулярки и матч, всеравно плохо.

А можно ли еще что то тут такое придумать, чтобы одновременно снизить скорость прохода и локальное потребление памяти?

Я понимаю, что сборщик памяти все очистит. Но он это делает довольно редко - раз в 3 минуты и мне как концепцию - можно ли сделать лучше?

Может можно еще что нибудь, что я сейас не вижу?

OBJECT_POSITION_PATTERNS = {}
for i = 1, 100 do  -- Важно: цикл до 100, а не 10!
    OBJECT_POSITION_PATTERNS[i] = "^" .. string.rep("...", i - 1) .. "(...)"
end

Например, я пробовал вот так обращаться к объектам. Но вроде это не лучше.

Короче, решено: самая оптимальная работа на луа со строками через саб. Быстрее и дешевле нет ничего. Прямое обращение саб к подстроке и все. Без локальных переменных и кэширований.

★★★★★

Последнее исправление: LightDiver (всего исправлений: 3)

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

Каждую итерацию
Но, тут тоже будет много холостых операций.

collectgarbage("step")

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


-- до входа в цикл обработки данных
local overmem = 1000
local trigger = collectgarbage("count") + overmem

-- внутри цикла обработки данных
local currmem = collectgarbage("count") 
if currmem >= trigger then 
   collectgarbage("collect")
   trigger = collectgarbage("count") + overmem
end

У тебя всё завязано на форматировании строк и конкатенациях, а это по сути самое медленное что в lua есть.

Избавляться от локальных переменных пробовал

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

Вместо превращений строк одни в другие может организовать структуры данных.
И обращатся не к подстрокам data:sub(blabla,blabla) а к полям данных data.key

А если нужно всё же сохранять поля данных в строку, для хранения например, то делать это лишь тогда когда это необходимо, как экспорт данных через string.format.

Больше мыслей нет

LINUX-ORG-RU ★★★★★
()
Последнее исправление: LINUX-ORG-RU (всего исправлений: 2)
Ответ на: комментарий от LINUX-ORG-RU

Сборка мусора ОЧЕНЬ тяжелая. Она вызывает подлагивание интерфейса и ее лучше вообще не трогать. Лучше изначально не плодить лишнего. Но тут надо еще и не допустить нагрузку на проц.

Понимаешь, у меня хранение «объектов» в строке:

["Шеф"] = {
		"00t00t00t00t00t0ka0ka0ka00t00t00t0ka00t0ka0ka00t0ka0ka00t00t00t00t00t0ka0ka0ka0ka00t0ka0ka00t0ka0ka0ka0ka0ka00t0ka0ob0ka0ka0ka00t00t00t0ka0ka0ka0ka0ka00t00t0ka0ka0ka00t0ka0ka0ka0ka00t0ka00t0ka0ka00t0ka0ka0ka0ka0ka0ka0ka0ka0ka0ka00t00t0ka0ka0ka0ka0ka00t0ka0ka0ka0ka0ka0ka0ka0ka00t0ka0ka0ka0ka0ka00t00t", -- [1]
		"4i{4jV4jH4ju Blnilnilnil4lh4nR4lsnil4nUnilnil4o8nilnil4mQ4l34kV4nZ4n=nilnilnilnil4mpnilnil4nxnilnilnilnilnil4q1nilnilnilnilnil4m/4p#4lAnilnilnilnilnil4pY4rAnilnilnil4mqnilnilnilnil4iknil4lLnilnil4ofnilnilnilnilnilnilnilnilnilnil4n94q$nilnilnilnilnil4mWnilnilnilnilnilnilnilnil4manilnilnilnilnil4p74nl", -- [2]
	},

Первая строка в массиве - объекты, вторая - их «здоровье». Но работа с этими строками очень быстрая.

/run for i=1,10000 do mFldS:addStaticStr("Шеф", 1, 5, "00t") end

Например 10 тысяч проходов вообще не заметны, там на уровне погрешности. Меньше 0.01с. Чтение тоже очень быстрое. Вдвое быстрее.

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

Мне нужно: получить строку. Тут я могу получить сразу ВСЮ строку, что я и делаю. То есть, мне не нужно дергать работу со строкой каждый раз. Нужно распарсить эту строку - пробить все сто объектов в ней. Если объекты нужные мне, обработать их хп и записать. Там тоже нет вроде нагрузки.

В теории нагрузки нет нигде. А на практике есть…

local objFull = mFldS:getStaticStr(sender, 1) or ""
local hpFull = mFldS:getStaticStr(sender, 2) or ""

Вот я получил обе строки. Вот как я могу «дешево» их обработать?

Я пробовал работу через таблицы. По сути было то же самое. Даже чуток хуже, кажись. Тут вопрос же не только в скорости, но и в памяти. Хотя, если невозможно локальную память оптимизировать, то хотелоь бы хотя бы скорость. Хрен с ней с памятью - очистится ведь через пару минут.

LightDiver ★★★★★
() автор топика
Последнее исправление: LightDiver (всего исправлений: 4)

Лучше всего можно оптимизировать если переписать всё на Си. Допускаю что даже затрат будет не сильно больше, а может и вообще не больше, чем у скриптооптимизаторского дроча.

firkax ★★★★★
()
Ответ на: комментарий от firkax

Да вот если бы. Я бы с удовольствием, а лучше на расте. Но такой возможности нет. Что можно: максимализировать использоание системных встроенных функций - они написаны на си. Минимализировать свои самописные.

Нужно придумать не затратную обработку строки из 300 символов.

LightDiver ★★★★★
() автор топика
Ответ на: комментарий от LightDiver

Строки в lua уникальны и неизменяемы, если ты изменил строку, значит ты её предыдущую копию выбросил в мусор. Если ты изменил 1000 строк типа

local str="hello"
      str=str.."world"

То сейчас в мусоре лежит указатель на две строки «hello» что была и «world» что временная. Ты не изменил строку в переменной str ты создал новую на основе двух, и эти две выкинул фпамойку.

В псевдосишке наивно выглядело бы это так

char * str = malloc(5+1); 
sprintf(str,"%s","hello");/*local str="hello"*/
char * stx = malloc(5+1); 
sprintf(stx,"%s","world"); /*анонимный "world"*/
char * buf = malloc(strlen(str)+strlen(stx)+1);
sprintf(buf,"%s%s",str,stx); /*str.."world"*/

/*Для сборщика мусора теперь есть, вот эта работа*/
free(str);
free(stx);

str = buf; /*str=str.."world"*/

Это просто пример, такого даже близко там не происходит. Но суть, откуда берётся мусор, думаю ясна.

Хочешь узнать откуда память, выполняй collectgarbage("count") после каждой операции и смотри на показатели, после чего именно они растут особенно сильно

Но, это я так, мысли в слух. Может кто умный придёт и что по делу скажет :)

LINUX-ORG-RU ★★★★★
()
Последнее исправление: LINUX-ORG-RU (всего исправлений: 2)
Ответ на: комментарий от LINUX-ORG-RU

Да понимаю я это все. Мы это обсудили еще полгода назад.

Я придумал быструю «дешевую» работу со строками. Оно работает. Вон два метода выше - один на чтение, другой на запись. Они работают практически со скоростью хэш-таблиц.

Вот допустим, я могу в функции один раз получить всю строку. Так? Так. Допустим, я тут же разбиваю ее на элементы. Или же дешевле каждый элемент распарсить? Я думаю затык именно в разбиении и соединении строки обратно локально в функции.

Я пока вижу два варианта работы: или в таблицу кадый элемент и по ним ходить или парсинг паттерном каждого элемента.

Тут надо понять что можно сделать лучше, от чего можно избавиться, а что лучше изначально не плодить. А может даже - есть другой какой подход.

От чего избавиться нельзя - от строки. Я долен получить строку 300 символов. От выделения КАЖДОГО элемента из нее. Мне надо прочитать из нее 100 элементов по 3 символа и сравнить их с двумя шаблонами.

Если шаблон совпад - обработали, записали в строку обратно.

Обработка по идее не затратная - получили значение объекта, приплюсовали ему единицу, записали обратно в строку. Все.

Это из обязательного.

LightDiver ★★★★★
() автор топика

Три раза прочитал весь код и всё равно не понял что он делает. Кажется это хороший сигнал чтобы выбросить всё и заново написать. Какая-то невероятно сложная структура input_table, которую эта функция невероятно сложно обновляет. Ну и обновлять длинную строку слайсами 100 раз это конечно дичь.

neumond
()
Ответ на: комментарий от neumond

Да нет там ничего сложного. Вон выше сама таблица. В таблице строки по 300 символов, разбитые в «объекты» по 3 символа. Все. Проходим паттерном по 3 символа и читаем или записываем нужный нам «объект».

https://github.com/Vladgobelen/NSQC3/blob/main/classes.lua#L67

Вот эти два метода. Там все видно и просто. Работает все максимально быстро и дешево. Проблема не тут.

Проблема в функции выше. Вот ее я и пытаюсь понять.

Заметь, я даже не читаю строку пообъектно, хотя могу. В этой функции я читаю строку куском. Это дешево… Хм, а что если мне не читать строку куском, не вводить новую обработку,, а моими методами работать с каждым объектом.. А это мысль, это надо попробовать.

LightDiver ★★★★★
() автор топика
Ответ на: комментарий от LightDiver

Обработка по идее не затратная - получили значение объекта, приплюсовали ему единицу, записали обратно в строку. Все.

Ну да и каждая копия этой строки уходит в сборку мусора

От выделения КАЖДОГО элемента из нее. Мне надо прочитать из нее 100 элементов по 3 символа и сравнить их с двумя шаблонами.

Ну и на 1000 трёхсотсимвольных строк после цикла остаётся сто тысяч маленьких кусочков, которые GC должен найти и удалить.

Короче, я не знаю что тут делать :(

LINUX-ORG-RU ★★★★★
()
Ответ на: комментарий от LINUX-ORG-RU

А другого вараинта нету. Это рост травы и деревьев. Вижу запрос о росте, проверяю, что объект = трава или дерево и итерирую им здоровье.

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

LightDiver ★★★★★
() автор топика
Ответ на: комментарий от LightDiver

Всё же попробуй дёграть collectgarbage("collect") перед или после цикла. Или дёргать collectgarbage("count") в конце каждого цикла. Так ты ничего не ускоришь, но вероятно избавишься от пролага на серваке, размазав сборку мусора во времени.

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

Ну, и как вариант ещё используй корутины, не обходи все данные деревков и травков сразу, а размазывай вычисления во времени. Ну по строке на кадр или как там у тебя игровой цикл идёт. Тогда и GC будет приходить чаще и делать свои дела быстрее, незаметнее.

LINUX-ORG-RU ★★★★★
()
Последнее исправление: LINUX-ORG-RU (всего исправлений: 1)
Ответ на: комментарий от LightDiver

Примерно так

t = NsDb:startUpdating(nik, nStr)
-- t = {8, 29, 32, 99, ...}

-- здесь вся логика, без кодировок в строки, тупо числами

NsDb:finishUpdating(nik, nStr, t)
-- здесь обратно всё пакуется в строку

Если есть встроенные какие-то средства сериализации, их лучше использовать чем свою кодировку. К примеру тот же жсон, реализация должна быть на си, будет быстрее.

neumond
()
Ответ на: комментарий от neumond

Я суть понял. Идея хорошая со сборкой строки. Разбираем, обрабатываем, в коце собираем и записываем куском. Я обдумаю это все и попробую. Спасибо.

LightDiver ★★★★★
() автор топика
Ответ на: комментарий от neumond

Бывает порезанная луа?

Конечно, в том её вся и суть lua. Песочницы можно делать, анально огороженные

Обычно есть набор дополнительных встроенных функций и модулей, специфичных приложению.

Да, всё верно, а всё иное нещадно выпиливается или подменяется удаляется динамически, например наружу торчит скрипт для руления ну скажем погодой и вычисления этого. Зачем такому скрипту доступ к io и os например =)

Ну и debug первое что летит под запрет, так как это отладчик и им можно уронить lua доведя до ошибки сегментирования, ибо это как gdb можно на живую ковырять внутри.

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

LINUX-ORG-RU ★★★★★
()
Ответ на: комментарий от LINUX-ORG-RU

И замечу, это плюс луа, как языку. Отличный язык на самом деле. Просто нужно понимать эти нюансы и использовать их.

Я может себе еще ДЕ напишу на луа по приколу. Основной на раст, а параллельно на луа для примера.

LightDiver ★★★★★
() автор топика
Последнее исправление: LightDiver (всего исправлений: 1)
Ответ на: комментарий от LINUX-ORG-RU

А это все ты виноват. Кто мне два года назад подсунул луа?

Я все эти два года практически 24/7 сидел, ковырял его и писал код…

LightDiver ★★★★★
() автор топика
Последнее исправление: LightDiver (всего исправлений: 1)
Ответ на: комментарий от LightDiver

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

Я все эти два года практически 24/7 сидел, ковырял его и писал код…

Ну надеюсь не в ущерб другим аспектам жизни. Зато у тебя своё царство государство есть и ты в нём архитектор, как в матрице :)

LINUX-ORG-RU ★★★★★
()
Последнее исправление: LINUX-ORG-RU (всего исправлений: 1)
Ответ на: комментарий от LINUX-ORG-RU

А, в этом смысле порезанная, что io нет. Такое да, для меня это скорее стандартная ситуация. Так-то Иерусалимски там знатно порезал уже, даже регексы свои сделал попроще, и стдлибы даже для стандартных типов всегда не хватает, хоть она и сильно гибче, чем у всех остальных, редко у кого тот же gsub умеет функцию применять.

neumond
()
Ответ на: комментарий от neumond

На самом деле в нашей песочнице луа гораздо более порезанный и переделанный. Тут оптимизированное глобальное интернирование строк динамическое, единое пространство, многие функции переписаны, какие то просто запрещены…

LightDiver ★★★★★
() автор топика
Ответ на: комментарий от LINUX-ORG-RU

А раст это я уже сам нашел. Сижу я такой последний год и думаю: «Блин, как хорошо было бы модули к луа на си писать нормальные, быстрые. Да и вообще сишку изучить, как низкоуровневую..»

И так меня это грызло грызло. А тут смотрю, на ЛОР раст хейтят. Загуглил - опа. А это ж та же самая сишка, только необычная и интересная…а ее еще и хейтят. Раз хейтят - значит надо брать, значит вещь стоящая. Ну я и взялся изучать. А оно оказалось и правда классное.

LightDiver ★★★★★
() автор топика
Ответ на: комментарий от LINUX-ORG-RU

Скорее всего решение в переходе на event модель. Вместо прохода в цикле по всем объектам используется событие которое вызывается у нужных объектов самих движком при наступлении определенных действий. Например, UNIT_COMBAT event вызывается при получении урона, хиле и тд. Возвращаемые аргументы:

arg1 - the UnitID of the entity
arg2 - Action,Damage,etc (e.g. HEAL, DODGE, BLOCK, WOUND, MISS, PARRY, RESIST, ...)
arg3 - Critical/Glancing indicator (e.g. CRITICAL, CRUSHING, GLANCING)
arg4 - The numeric damage
arg5 - Damage type in numeric value (1 - physical; 2 - holy; 4 - fire; 8 - nature; 16 - frost; 32 - shadow; 64 - arcane)

что-то типа:

do
    local function UNIT_COMBAT( event, unit, action, _, amount )
        if amount > 0 and action == 'HEAL' then
            // какое-то действие
        end
    end
end
Obezyan
()
Ответ на: комментарий от Obezyan

Ооо.. Комбатлог это искусство. Знал бы ты сколько там вариантов этих событий, каждое со своим набором аргументов. Но тут это нам не подходит.

В нашем случае событие генерирует аддон пользователя раз в 100 секунд и отправляет запрос на аддон-сервер мне. Этот запрос я должен обработать: читаем поле игрока. Поле из 100 объектов. Если объект «дерево» или «Трава» - оно должно вырасти на единицу. Если «трава» выросла больше 999 единиц хп, она превращается в дерево.

Это почти игра в игре своеобразная. Просто для созданная для удобства управления квестами пользовательскими кастомными. Вместо кнопокм с квестами такие вот интерактивные объекты. Долго объяснять.

https://s.iimg.su/s/23/dHXgSI5ela0LxognnsFKXrA5OPUo1EfyBUKtyKkM.jpg

Вот видишь? Это пример поля игрока. Вон у меня внизу слева растет трава.

А вон в чатике еще ниже какой то игрок выполнил гильдейский квест и получил опыт.

LightDiver ★★★★★
() автор топика
Последнее исправление: LightDiver (всего исправлений: 1)
Ответ на: комментарий от Obezyan

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

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

LINUX-ORG-RU ★★★★★
()
Ответ на: комментарий от LightDiver

Ооо.. Комбатлог это искусство. Знал бы ты сколько там вариантов этих событий, каждое со своим набором аргументов. Но тут это нам не подходит.

А вдруг, чисто случайно, я знаю. Также как например знаю SQF код Армы в том числе написание модов и dll к ней.

В нашем случае событие генерирует аддон пользователя раз в 100 секунд и отправляет запрос на аддон-сервер мне

А цикл вы делаете по вот этому квадратному полю 10x10? Если да то зачем вы отправляете и объекты и их здоровье? Можно же просто отправлять массив из 100 чисел-элементов который содержит только здоровье:

data = {1, 999, 34, 100 ...}

Считая что этот массив заполняет квадрат слева направо сверху вниз:

for i = 1, #data, 1 do
    local hp = a[i]
    -- i - столбец квадрата, j - строка квадрата, начиная с 1
    local j = math.floor(i/10) + 1
end

Дальше зная клетку (строка+столбец) можно сравнить 999 c hp и дальше применить вашу логику. Никаких объектов только цикл по 100 числам. Наиболее быстрое в вашем случае. Я могу бы предложить еще битовые маски, но в данном случае это оверхед.

Obezyan
()
Ответ на: комментарий от Obezyan

У клиента только графика. Вся логика на «сервере» у меня. Мне не надо ничего отправлять. Все данные пользователя уже у мен в таблице вот в таком виде:

["Шеф"] = {
		"00f00t00t00t00t0ka0ka0ka00t00t00t0ka00t0ka0ka00t0ka0ka00t00t00t00t00t0ka0ka0ka0ka00t0ka0ka00t0ka0ka0ka0ka0ka00t0ka0ob0ka0ka0ka00t00t00t0ka0ka0ka0ka0ka00t00t0ka0ka0ka00t0ka0ka0ka0ka00t0ka00t0ka0ka00t0ka0ka0ka0ka0ka0ka0ka0ka0ka0ka00t00t0ka0ka0ka0ka0ka00t0ka0ka0ka0ka0ka0ka0ka0ka00t0ka0ka0ka0ka0ka00t00t", -- [1]
		"  >4kk4kf4lA  /nilnilnil4n24o[4n#nil4o}nilnil4pTnilnil4nh4m$4l[4p;4oYnilnilnilnil4oDnilnil4p+nilnilnilnilnil4r)nilnilnilnilnil4nd4qb4mLnilnilnilnilnil4r04sOnilnilnil4o#nilnilnilnil4j'nil4mVnilnil4p[nilnilnilnilnilnilnilnilnilnil4oI4rOnilnilnilnilnil4nqnilnilnilnilnilnilnilnil4o5nilnilnilnilnil4qK4pB", -- [2]
	},
["Витинари"] = {
		"0ka0ka00t00t0ka0ka0ka0ka00t00t00t0ka00t0ka0ka00t0ka0ka00t00t00t00t00t0ka0ka0ka0ka00t0ka0ka00t0ka0ka0ka0ka0ka00t0ka0ob0ka0ka0ka00t00t00t0ka0ka0ka0ka0ka00t00t0ka0ka0ka00t0ka0ka0ka0ka00t0ka00t0ka0ka00t0ka0ka0ka0ka0ka0ka0ka0ka0ka0ka00t00t0ka0ka0ka0ka0ka00t0ka0ka0ka0ka0ka0ka0ka0ka00t0ka0ka0ka0ka0ka00t00t", -- [1]
		"nilnil Br Bpnilnilnilnil Bt Br Bonil Bqnilnil Bsnilnil Bp Bp Br Bp Bonilnilnilnil Brnilnil Bonilnilnilnilnil Btnilnilnilnilnil Bl Bp Bnnilnilnilnilnil Bo Btnilnilnil Btnilnilnilnil Bpnil Bpnilnil Btnilnilnilnilnilnilnilnilnilnil Bu Brnilnilnilnilnil Bsnilnilnilnilnilnilnilnil Brnilnilnilnilnil Bs Bm", -- [2]
	},

Первая строка массива - объекты по три символа на объект, вторая - их хп точно так же по три символа на объект.

В обработке поля нет проблем, ибо это одномерный массив из 100 объектов от 1 до 100 на самом деле.

Пользователь кликает что то у себя, отправляет запрос на «сервер» а сервер у меня уже обрабатывает логику и отправляет результат пользователю. Все просто.

Сейчас вот перебираю варианты работы с объектами более менее оптимальные. Пробую варианты работа «на лету» и разборки строки на составляющие, обработки элементов и сборки обратно.

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

LightDiver ★★★★★
() автор топика
Ответ на: комментарий от LightDiver

Все данные пользователя уже у мен в таблице вот в таком виде

А зачем тогда цикл по 100 элементам? Вам на сервер от пользователя отправляется же индекс кликнутого элемента (1..100)?

Если поменять вид на:

["Шеф"] = {
   1 = {
     "type" = 0,
     "hp" = 123
   },
   2 = {
     "type" = 0,
     "hp" = 50
   },
   4 = {
     "type" = 1,
     "hp" = 999
   },
   ...

   100 = {
     "type" = 0,
     "hp" = 10
   },
}

То получив от пользователя индекс idx кликнутого квадрата можно сразу получить его свойства data["Шеф"][idx]

Сборка/разборка строки это неоптимальное решение.

Obezyan
()
Ответ на: комментарий от Obezyan

Вот с этого я и начинал, да. Так гораздо проще быстрее работать. Но есть нюанс. Это луа5.1, где количество элементов ограничено 232 тысячами. Представь, допустим 10 таблиц по 100 элементов на пользователя. Это 1000 клюей, 1000 строк-значений. Плюс доп настройки, плюс доп элементы. На каждого. Всего сто пользователей и у тебя уже от ста тысяч таких элементов. При экономии все падает на 3-4 тысячах пользователей.

А падает оно, обнуляя все таблицы. Просто в nil. Все таблицы всех аддонов. Правда здорово? Все данные.

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

В текущем варианте обработки можно 30-50 тысяч пользователй одновременно, что с большим запасом перекрывает потребности. Плюс интернирование, унификация, очистка неиспользующегося.

Заметь, я даже не использую ключи. У меня не хэш-таблицы. У меня просто массив строк. Это экономия 20-30% места, что на большом объеме данных очень неплохо. И работа с номерами строк, а не ключами.

Вот обрати внимание на твой вариант структуры. Это СТО таблиц базово вместо одной строки. Каждая таблциа 40 байт. Умножай на пользователя. В каждой таблице по две строки по 17 байт плюс данные… Это плюс двести строк. Плюс двести ключей по 17 байт плюс данных.. У меня все аж зачесалось от такой структуры, ибо я пробовал такое даже в более оптимальном варианте. И даже в более оптимальном варианте все рухнуло в итоге.

LightDiver ★★★★★
() автор топика
Последнее исправление: LightDiver (всего исправлений: 4)
Ответ на: комментарий от LightDiver

Это луа5.1, где количество элементов ограничено 232 тысячами.

Так храните эти данные в сериализованом виде в SavedVariables. У аддона могут быть свои SavedVariables и у них нет лимита насколько я помню.

[«Шеф»] = сериализованная строка (как это делают Lib: Serialize/AceSerializer).

Получили сериализованную строку по uid (Шеф), затем десериализовали ее в

{
   1 = {
     "type" = 0,
     "hp" = 123
   },
   ...
}

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

Я бы еще попробовал бинарную сериализацию вида:

function serializeToBinary(data)
    local serialized = ""
    
    for k, v in pairs(data) do
        serialized = serialized .. string.pack(">s2s2", k, v)
    end
    
    return serialized
end

function deserializeFromBinary(binaryData)
    local deserialized = {}
    local pos = 1
    local len = #binaryData
    
    while pos <= len do
        local key, value
        key, pos = string.unpack(">s2", binaryData, pos)
        value, pos = string.unpack(">s2", binaryData, pos)
        
        deserialized[key] = value
    end
    
    return deserialized
end

но я не помню поддерживает ли WOW-кий Lua pack/unpack.

Obezyan
()
Ответ на: комментарий от Obezyan

Именно там и храню. Но при загрузке оттуда в память (при входе в игру), все рушится.

Не, нету тут еще анпака и как же мне его не хватает.

Я сделал довольно оптимальную обработку строк. Там скорость работы на 1000 проходов что то около 0.006с. Скорость чтения 1000 проходов около 0.003с. Ниже тупо нельзя, я проверял. Ну, точнее, ниже 1000 проходов нельзя измерить время, будут просто нули.

Это почти сравнимо с использованием хэш-таблиц плюс минус. В этом плане все нормально.

LightDiver ★★★★★
() автор топика
Последнее исправление: LightDiver (всего исправлений: 2)
Ответ на: комментарий от LINUX-ORG-RU

Смотри, кажись я перехитрил сам себя:

function time100_Server(channel, text, sender, prefix)
    if prefix == "time100" then 
        -- Получаем полные строки данных
        local objFull = mFldS:getStaticStr(sender, 1)
        local hpFull = mFldS:getStaticStr(sender, 2)
        
        -- Преобразуем строки в таблицы для удобной модификации
        local objTable = {}
        local hpTable = {}
        
        -- Заполняем таблицы объектов и значений (разбиваем на группы по 3 символа)
        for i = 1, math.max(#objFull, #hpFull), 3 do
            objTable[#objTable+1] = objFull:sub(i, i+2)
            hpTable[#hpTable+1] = hpFull:sub(i, i+2)
        end

        -- Основной цикл обработки объектов
        for idx = 1, #objTable do
            local obj = objTable[idx]
            local hp = hpTable[idx]:gsub("%s+", "") -- Убираем пробелы перед декодированием
            
            if obj == "00t" then
                -- Обработка активного объекта
                local decoded = en10(hp)
                
                if decoded < 999 then
                    -- Увеличиваем значение на 1, если оно меньше 999
                    hpTable[idx] = string.sub("   "..en85(decoded + 1), -3)
                else
                    -- Если значение 999 или больше, увеличиваем с шансом 10%
                    local x = math.random(1, 10)
                    if x == 10 then
                        hpTable[idx] = string.sub("   "..en85(decoded + 1), -3)
                    end
                end
                
            elseif obj == "00f" then
                -- Обработка неактивного объекта
                local decoded = en10(hp)
                
                if decoded < 999 then
                    -- Увеличиваем значение на 1, если оно меньше 999
                    hpTable[idx] = string.sub("   "..en85(decoded + 1), -3)
                else
                    -- Заменяем тип объекта при достижении максимума
                    objTable[idx] = "00t"
                end
            end
        end

        -- Собираем результаты обратно в строки
        local newObjStr = table.concat(objTable)
        local newHpStr = table.concat(hpTable)

        -- Записываем обновленные данные
        mFldS:addStaticStr(sender, 1, nil, newObjStr)
        mFldS:addStaticStr(sender, 2, nil, newHpStr)
    end
end



function time100_Server1(channel, text, sender, prefix)
    if prefix == "time100" then 

        for i = 1, 100 do
            if mFldS:getStaticStr(sender, 1, i) == "00t" then
                if en10(mFldS:getStaticStr(sender, 2, i)) < 999 then
                    mFldS:addStaticStr(sender, 2, i, string.sub("   "..en85(en10(mFldS:getStaticStr(sender, 2, i)) + 1), -3))
                else
                    local x = math.random(1, 10)
                    if x == 10 then
                        mFldS:addStaticStr(sender, 2, i, string.sub("   "..en85(en10(mFldS:getStaticStr(sender, 2, i)) + 1), -3))
                    end
                end
            end
            if mFldS:getStaticStr(sender, 1, i) == "00f" then
                if en10(mFldS:getStaticStr(sender, 2, i)) < 999 then
                    mFldS:addStaticStr(sender, 2, i, string.sub("   "..en85(en10(mFldS:getStaticStr(sender, 2, i)) + 1), -3))
                else
                    mFldS:addStaticStr(sender, 1, i, "00t")
                end
            end
        end
    end
end

Я сделал две функции. Первую типа оптимально. Распаковал, обработал, запаковал. Вторую сделал без выпендрежа тупо через свои методы. Прочитал конкретное значение в строке, сравнил, записал. Напрмую. Так вот. Вторая функция при 1000 проходов жрет вдвое меньше памяти, чем первая, это раз. Во-вторых, в 8 раз меньше, чем мои прошлые варианты. 2мб озу при 1000 проходов вместо 16мб.

А вот со скоростью забавное. При малых значениях от 1 до 500 проходов за раз, вторая функция ВДВОЕ быстрее первой. Вот это поворот, а? При 5000+ проходов вторая функция сравнима с первой по скорости или чуууть чуть медленнее.. То есть вторая замедляется при увеличении количества проходов. Что то там накапливается. Но это не смертельно, мне такое количество за раз и не нужно - у меня малое число проходов единовременно.

Получается не надо было выпендриваться.. Или же есть вариант оптимизации второй? Хмм.. Надо еще подумать.

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

LightDiver ★★★★★
() автор топика
Последнее исправление: LightDiver (всего исправлений: 5)
Ответ на: комментарий от LightDiver

Или же есть вариант оптимизации второй

function time100_Server1(channel, text, sender, prefix)
    if prefix == "time100" then
        for i = 1, 100 do
            local staticStr1 = mFldS:getStaticStr(sender, 1, i)
            local staticStr2 = mFldS:getStaticStr(sender, 2, i)
            local staticStr2Value = en10(staticStr2)
            
            if staticStr1 == "00t" then
                if staticStr2Value < 999 then
                    mFldS:addStaticStr(sender, 2, i, string.sub("   "..en85(staticStr2Value + 1), -3))
                else
                    if math.random(1, 10) == 10 then
                        mFldS:addStaticStr(sender, 2, i, string.sub("   "..en85(staticStr2Value + 1), -3))
                    end
                end
            elseif staticStr1 == "00f" then
                if staticStr2Value < 999 then
                    mFldS:addStaticStr(sender, 2, i, string.sub("   "..en85(staticStr2Value + 1), -3))
                else
                    mFldS:addStaticStr(sender, 1, i, "00t")
                end
            end
        end
    end
end
Obezyan
()
Ответ на: комментарий от Obezyan

Забавно, но этот вариант чуток похуже. По памяти по сути то же самое, а вот по скорости отстает даже от моего «оптимизированного». Совсем немного, но отстает. А мой "неоптимизированный, из которого получилась твоя функция, быстрее (вдвое) на малых значениях.

Возможно так и стоит оставить без попыток улучшений. Всетаки в классе рабоыт с БД все достаточно оптимально и так.

Возможно вот этот твой вариант стоит взять просто из за лушей читабельности.. Хотя, а может и стоит оставить неоптимизированное из за скорости. Нужно еще сутки подумать. Если ничего не придумаю, оставлю один из неоптимизированных и пойду дальше.

Да, наверное стоит мой последний вариант закомментировать подробно и оставить.

LightDiver ★★★★★
() автор топика
Последнее исправление: LightDiver (всего исправлений: 2)
Ответ на: комментарий от anonymous

С чатом члор дел не стоит иметь никому и никогда. Тебе, как его участнику, это известно лучше других. Не нужно флудить тут. Я что, не поставил защиту от анонимов?

LightDiver ★★★★★
() автор топика
Последнее исправление: LightDiver (всего исправлений: 1)

Мне не нравятся вытаскивание подстроки ради сравнения с шаблоном.

local obj = sub(...)
if obj == 'шаблон' ...

Можно сразу string.match(str, pattern, pos), не будут создаваться временные строки

Также «кодеки» en10 и т.п. переделать, чтобы не вытаскивали подстроки, а работали по месту en10(hpFull, pos).

Максимально уменьшить создание временных значений.

anonymous
()
Ответ на: комментарий от anonymous
-- Локализация системных функций
local abs, floor = math.abs, math.floor
local byte, sub, char = string.byte, string.sub, string.char
local tbl_insert, tbl_concat, error = table.insert, table.concat, error

local _convertTable3 = {
    [0] = "0", [1] = "1", [2] = "2", [3] = "3", [4] = "4",
    [5] = "5", [6] = "6", [7] = "7", [8] = "8", [9] = "9",
    [10] = "A", [11] = "B", [12] = "C", [13] = "D", [14] = "E",
    [15] = "F", [16] = "G", [17] = "#", [18] = "$", [19] = "%",
    [20] = "(", [21] = ")", [22] = "*", [23] = "+", [24] = "-",
    [25] = "/", [26] = ";", [27] = "<", [28] = "=", [29] = ">",
    [30] = "@", [31] = "H", [32] = "I", [33] = "J", [34] = "K",
    [35] = "L", [36] = "M", [37] = "N", [38] = "O", [39] = "P",
    [40] = "Q", [41] = "R", [42] = "S", [43] = "T", [44] = "U",
    [45] = "V", [46] = "W", [47] = "X", [48] = "Y", [49] = "Z",
    [50] = "^", [51] = "_", [52] = "`", [53] = "a", [54] = "b",
    [55] = "c", [56] = "d", [57] = "e", [58] = "f", [59] = "g",
    [60] = "h", [61] = "i", [62] = "j", [63] = "k", [64] = "l",
    [65] = "m", [66] = "n", [67] = "o", [68] = "p", [69] = "q",
    [70] = "r", [71] = "s", [72] = "t", [73] = "u", [74] = "v",
    [75] = "w", [76] = "x", [77] = "y", [78] = "z", [79] = "{",
    [80] = "|", [81] = "}", [82] = "[", [83] = "]", [84] = "'",
}
-- Обратная таблица конвертации
local _reverseConvertTable3 = {}
for k, v in pairs(_convertTable3) do
    _reverseConvertTable3[v] = k
end
-- Максимальное поддерживаемое число (85^12)
local MAX_NUMBER = 85^12
-- Буфер для кодирования
local encode_buffer = {}
-- Кодирование числа в строку
function en85(dec)
    if type(dec) ~= "number" then error("Input must be a number") end
    if dec == 0 then return "0" end
    -- Проверка диапазона
    if dec < 0 or dec > MAX_NUMBER then
        error("Number out of range: " .. tostring(dec))
    end
    local idx = 0
    repeat
        local remainder = dec % 85
        dec = floor(dec / 85)
        idx = idx + 1
        encode_buffer[idx] = _convertTable3[remainder]
    until dec == 0
    -- Сборка строки в правильном порядке (обратном)
    local result = ""
    for i = idx, 1, -1 do
        result = result .. (encode_buffer[i] or "")
    end
    -- Очистка буфера
    for i = 1, idx do
        encode_buffer[i] = nil
    end
    return result
end
-- Декодирование строки в число
function en10(encoded)
    if type(encoded) ~= "string" then return 0 end
    if encoded == "0" then return 0 end
    local number = 0
    local len = #encoded
    for i = 1, len do
        local symbol = sub(encoded, i, i)
        local digit = _reverseConvertTable3[symbol] or 0
        number = number * 85 + digit
    end
    return number
end

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

Пока по результатам тестов самый быстрый и нежрущий вариант Луа5.1 и параноидальная оптимизация (комментарий) тут второй.

И да, string.match я тоже пробовал. Оно чуть чуть хуже использования саба:

-- Шаблон для доступа к номеру элемента без пробелов
OBJECT_POSITION_PATTERNS = {}
for i = 1, 100 do
    OBJECT_POSITION_PATTERNS[i] = "^" .. string.rep("...", i - 1) .. "(...)"
end

Вот по такому паттерну.

LightDiver ★★★★★
() автор топика
Последнее исправление: LightDiver (всего исправлений: 3)
Ответ на: комментарий от LightDiver

Нужно что то сделать - делай, сравнивай.

В job. Если тебе не нужны подсказки/обсуждение, а нужен готовый разультат.

sub(encoded, i, i)

Создает временную строку. Можно попробовать string.byte

anonymous
()
Ответ на: комментарий от anonymous

С это я и начал, можешь обратить внимание, в самом начале. Это плохо получилось. Засирало память по 16мб на 1000 проходов и работало достаточно медленно. Но там я использовал кучу локальных переменных и дублировал по сути обработку.

Этот рабочий вариант с сабом УЖЕ используется мною в методах работы со строкой. Потому самый лучший вариант - использовать эти методы напрямую и не городить велосипед. Наверное так. Пока других вариантов не вижу.

Я сначала пытался получать всю строку, разбивать ее, обрабатывать и собирать. А надо было не строку получать, каждй объект в ней отдельно методом.

function NsDb:getStaticStr(nik, nStr, nArg)
    local str = self.input_table 
              and self.input_table[nik] 
              and self.input_table[nik][nStr]
    
    return str and (nArg and str_sub(str, (nArg-1)*3 + 1, nArg*3) or str)
end

Вот я в классе уже получаю каждый объект максимально быстро. Я хз как это сделать еще лучше. Все остальное будет просто вносить проблемы.

LightDiver ★★★★★
() автор топика
Последнее исправление: LightDiver (всего исправлений: 2)
Ответ на: комментарий от Obezyan

Аххахаха! У меня гениальная идея. А что если бремя поиска переложить на пользователей?

Самый оптимальный вариант сейчас - напрямую достать каждый объект и сравнить. Это лучше парсинга строки. Но пользователи могут мне прислать строку со своими объектами, которые нужно обработать. до 66% снижает длину строки для обработки. Гениально?

Пользователь читает свое поле, составляет номера нужных клеток, передает мне. Я обрабатываю ТОЛЬКО эти клетки.

LightDiver ★★★★★
() автор топика