Давайте решим вопрос оптимизированной разработки на Glua

Предлагаю накидать сюда советы по оптимизации кода в glua (с пруфами!!!) соберем все в кучку и я напишу большой пост на основе ваших выводов.

Что значит пруфы?
В сообщение пишем сам код (желательно с комментариями)
ниже результат бенчмарка с оптимизацией и без.

Открытые вопросы:
Реально ли локализация переменных помогает оптимизации?
Массивы зло или нет?
инлайн или ооп?

Быстрые хуки, если не ошибаюсь есть в SAM

Быстрая сериализация, замена в сапе pON-у

Там же есть бенчмарки в описании

2 лайка

Возможно не по теме, как дополнение
image

Спасибо, буду рад если докинешь пруфы)

1 лайк

Таблицы

Код
local next = next
local function ret(t, n, last)
	local c = t[n]
	for i = n, last or #t do
		t[i] = t[i + 1]
	end
	return c
end

function table.F_insert(t, T, n,last)
	local nt = last or #t
	if not n then
		t[nt + 1] = T
		return nt + 1
	end

	for i = nt, n, -1 do
		t[i + 1] = t[i]
	end

	t[n] = T
	return n
end

table.F_remove = ret
--Аналога нет
function table.F_fix(t, n)
    local p = 0
    local k = table_maxn(t) --Получаем последний ключ таблицы
    for i = 1, k or 0 do
        if t[i - p] ~= nil then continue end

        if t[i] == nil then
            p = p + 1
            continue
        end

        t[i - p] = t[i]
        t[i] = nil
    end
end

Процессор Ryzen 3 3600 ~4.2ГГц
Сравнение Table.Insert с кастомным, вызваем 1,000,000 раз.

Код бенчмарка

local T={}
local N=1000000
print(‘-’)
local start = os.clock()
for i=1,N do
table.insert(T, N)

end
print('Стандарт без локализации, без аргумента в конце: ',os.clock()-start,‘Секунд’)
T={}
start = os.clock()

for i=1,N do
table.insert(T, i, N)

end
print('Стандарт без локализации, с аргументов в конце: ',os.clock()-start,‘Секунд’)
T={}
start = os.clock()
local table_insert=table.insert
for i=1,N do
table_insert(T, N)

end

print('Стандарт с локализацией, без аргумента в конце: ',os.clock()-start,‘Секунд’)
T={}
start = os.clock()

for i=1,N do
table_insert(T, i, N)
end
print('Стандарт с локализацией, с аргументом в конце: ',os.clock()-start,‘Секунд’)

T={}
start = os.clock()
for i=1,N do
table.F_insert(T, N)
end
print('Кастом без локализации, без аргумента в конце: ',os.clock()-start,‘Секунд’)

T={}
start = os.clock()
for i=1,N do
table.F_insert(T, N,i)
end
print('Кастом без локализации, с аргументом в конце: ',os.clock()-start,‘Секунд’)
local table_F_insert=table.F_insert

T={}
start = os.clock()
for i=1,N do
table_F_insert(T, N)
end
print('Кастом с локализацией, без аргумента в конце: ',os.clock()-start,‘Секунд’)

T={}
start = os.clock()
for i=1,N do
table_F_insert(T, N,i)
end
print(‘Кастом с локализацией, с аргументом в конце: ‘,os.clock()-start,‘Секунд’)
print(’===’)
T={}
start = os.clock()
for i=1,N do
table.F_insert(T, N,i,i-1)
end
print('Кастом без локализации, с аргументом в конце, с #Table: ',os.clock()-start,‘Секунд’)

T={}
start = os.clock()
for i=1,N do
table.F_insert(T, N,nil,i-1)
end
print('Кастом без локализации, без аргумента в конце, с #Table: ',os.clock()-start,‘Секунд’)

T={}
start = os.clock()
for i=1,N do
table_F_insert(T, N,nil,i-1)
end
print('Кастом с локализацией, без аргумента в конце, с #Table: ',os.clock()-start,‘Секунд’)

T={}
start = os.clock()
for i=1,N do
table_F_insert(T, N,i,i-1)
end
print('Кастом с локализацией, с аргументом в конце, с #Table: ',os.clock()-start,‘Секунд’)

Результат

Стандарт без локализации, без аргумента в конце: 0.052999999999997 Секунд
Стандарт без локализации, с аргументов в конце: 0.085000000000008 Секунд
Стандарт с локализацией, без аргумента в конце: 0.051999999999992 Секунд
Стандарт с локализацией, с аргументом в конце: 0.079999999999984 Секунд

Кастом без локализации, без аргумента в конце: 0.053000000000026 Секунд
Кастом без локализации, с аргументом в конце: 0.058999999999997 Секунд
Кастом с локализацией, без аргумента в конце: 0.055999999999983 Секунд
Кастом с локализацией, с аргументом в конце: 0.055000000000007 Секунд

Кастом без локализации, с аргументом в конце, с #Table: 0.0049999999999955 Секунд
Кастом без локализации, без аргумента в конце, с #Table: 0.0039999999999907 Секунд
Кастом с локализацией, без аргумента в конце, с #Table: 0.0040000000000191 Секунд
Кастом с локализацией, с аргументом в конце, с #Table: 0.0049999999999955 Секунд

Сравнение Table.Remove с кастомным, вызваем 1,000 раз, в таблице с 1,000,000 ключей

Код бенчмарка
local T={}
local N=1000000
for i=1,N do 
T[i]=N
end
local table_remove=table.remove
local table_F_remove=table.F_remove
local start = os.clock()
for i=1,1000 do 
table.remove(T)
end
print('Стандарт без локализации, без аргумента : ',os.clock()-start,'Секунд')
T={}
for i=1,N do 
T[i]=N
end


local start = os.clock()

for i=1,1000 do 
table_remove(T, 1)
end
print('Стандарт с локализацией, без аргумента : ',os.clock()-start,'Секунд')
T={}
for i=1,N do 
T[i]=N
end

for i=1,N do 
T[i]=N
end

local start = os.clock()
for i=1,1000 do 
table.remove(T, 1)
end
print('Стандарт без локализации, с аргументом : ',os.clock()-start,'Секунд')

T={}
for i=1,N do 
T[i]=N
end


local start = os.clock()

for i=1,1000 do 
table_remove(T, 1)
end
print('Стандарт с локализацией, с аргументом : ',os.clock()-start,'Секунд')
T={}
for i=1,N do 
T[i]=N
end
local start = os.clock()

for i=1,1000 do 
table.F_remove(T, 1)
end
print('кастом  без локализации, с аргументом : ',os.clock()-start,'Секунд')
T={}
for i=1,N do 
T[i]=N
end
local table_F_remove=table.F_remove

local start = os.clock()

for i=1,1000 do 
table_F_remove(T, 1)
end
print('кастом  с локализацией, с аргументом  ',os.clock()-start,'Секунд')
T={}
for i=1,N do 
T[i]=N
end
local start = os.clock()

for i=1,1000 do 
table.F_remove(T,N-(i-1))
end
print('кастом  без локализации, без аргумента : ',os.clock()-start,'Секунд')
T={}
for i=1,N do 
T[i]=N
end


local start = os.clock()

for i=1,1000 do 
table_F_remove(T,N-(i-1))
end
print('кастом  с локализацией, без аргумента: ',os.clock()-start,'Секунд')
Результат

Стандарт без локализации, без аргумента : 0 Секунд
Стандарт с локализацией, без аргумента : 0 Секунд
Стандарт без локализации, с аргументом : 1.215 Секунд
Стандарт с локализацией, с аргументом : 1.21 Секунд
Кастом без локализации, с аргументом : 0.673 Секунд
Кастом с локализацией, с аргументом 0.673 Секунд
Кастом без локализации, без аргумента : 0 Секунд
Кастом с локализацией, без аргумента: 0 Секунд

Кастомный Table.Remove не поддерживает отсутствие аргумента, по этому был имитирован через N-(i-1), где N колличество изначальных ключей, где i сколько отнимаем через for. Но возвращает удалённое значение!

Так же в коде предоставлена доп функция table.F_fix убирает пустоты в таблицах
[1]=1,[2]=2,[6]=6,[50]=50 |==> [1]=1,[2]=2,[3]=6,[4]=50

1 лайк

не существует никаких плюшек на удивление большинству. Ты либо минимально понимаешь, как работает машина у тебя под ногами, либо ты пишешь медленный код. И никакие “плюшки” тебе не помогут.

По моим перегруженным (в плане лишнего мусора) тригону и ГМД не скажешь, но после них и книжки “эссенциализм” пришло понимание простой и банальной вещи, которая должна была бы быть вбита в голову каждому с рождения: ЛЕСТНИЦА ДОЛЖНА БЫТЬ ПРИСТАВЛЕНА К ПРАВИЛЬНОЙ СТЕНЕ В ПРАВИЛЬНОМ МЕСТЕ. Где под лестницей я подразумеваю путь решения какой-либо задачи.

Ты можешь написать тысячу оптимизированных плюшек и функций, потратить на оптимизацию кучу долороф и твоя разработка останется ненужным куском :poop:. А можешь быть трололо-школоло, написать мини-продукт из 10 крутых функций на коленке, которые будут работать медленно и багованно, но при этом это все равно будет решать 99% потребности потребителя и пофиг всем будет на то, что если бы этих функций была тысяча, то продукт лагал бы.

Короче говоря. Не в оптимизации счастье, а в том, чтобы не написать лишнего. Как SUP в свое время. Написали минимум, получили максимум. И хороший код это побочка, а не причина их успеха.


P.S. Так и с этой темой. Нет смысла обсуждать оптимизацию. Вы все равно примените ее там, где от нее не будет толку. Как и эта тема находится там, где от нее не будет толку

2 лайка

Нужен пример, почему “оптимизаторы” пусть лучше голову себе оптимизируют?

В этой же теме выше.

  1. Создаешь “оптимизированную” функцию
  2. Делаешь контрфункцию, чтобы фиксить последствия своей оптимизированной функции
  3. :clown_face:

В каком реальном(!!!) юз кейсе может пригодится функция table.fix для удаления дыр в таблице?

Правильной оптимизацией в случае @profef было бы избавиться от случаев, когда его функции ему нужны

1 лайк

сколько бы “оптимизиционных плюшек” ты не знал, достаточно провтыкать разочек в Think какой-то кирпич, не знать, что такое профайлер и весь твой сервер с хохотом будет кровью плеваться тебе в лицо.

Для множественного удаление ключами без функций table.Remove, после чего сдвинуть все 1 раз, а не переберать для каждого ключа.
Как пример

--любой хук: Think,Tick,Render и т.д
local TableN=#Table
local FIX=false
for i=1,TableN do 
if IsValid(Table[i]) then continue end
Table[i]=nil
FIX=true
end
if not FIX then return end--если не удаляли ничего в таблице, то не используем fix
table.F_fix(Table)

это вразы лучше чем использовать table.remove для каждого n ключа в таблице с колличеством m ключей.

Я сказал «реальный пример», а не выдумать абстрактную проблему в голове и назвать ее. Чувствуешь разницу?

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

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

До сих пор жду. Мне действительно интересно зачем твоя функция существует

Честно говоря, использовал в крайне узкоспециализированной функции отрисовки эффектов (Пули). Когда координата направления конца полёта не видна — эффект не рендерится; поэтому засунул в отдельный хук рендера. Однако эти элементы не имеют аналога ENTITY:OnRemove и могут исчезнуть раньше, чем завершится предыдущий эффект.
Но таких эффектов может быть больше 20-30, то каждый раз использовать table.remove не разумно, проще с помощью функции сдвинуть за 1 for (для оптимизации 1 хук) и не действительные удаляю с таблицы (Table[N]=nil).
Кстати изменил код самой функции в теме, так как next показывал не то что мне нужно, изменил на правильную функцию.

По этому не могу дать Пример который подойдёт для всего, только узкоспециализированные места таких как рендер.

Не уверен, что понял. Допустим, ты хранишь в таблице информацию о попадании пуль, чтобы в каком-то хуке проверять, видимы ли они и если да, то отрисовывать.

Вопрос раз: в чем проблема наличия “дыр” в такой таблице?

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

Два. Ты создаешь баг на ровном месте.

Если ты проходишься по таблице через ipairs, при этом “какие-то моменте времени” там может быть дыра – у тебя не отрисуются все пули после дыры. Их не будет существовать до момента срабатывания твоей ненужной Fix функции. Даже если это микросекунда. Ты создал трудновоспроизводимый баг на ровном месте.

Два с половиной. Ты создал неожиданное поведение кода.

Разработчики не нужны думать, что где-то в стороне дыра пофиксится. Код должен быть читаемым изначально. Либо с явным scheduling’ом, который будет стоить тебе еще вычислительных ресурсов и читаемоссти кода.

Я не использовал ipairs, только for i =1,#Table , изначально дыр нет, я создаю их сам за место невалидных эффектов, так же я не проверяю видно эффект или нет так как знаю что выгоднее просто отрисовать его принудительно их может быть много, но не более ~100(зависит от активности) по этому проверка на видимость не нужна так как она сама по себе весомая нагрузка в рендер хуках

тоже диагноз

Ок, pairs использовать?

комбинировать. В 98% мест ipairs/pairs, да

Пример можно? Я не использу их из за сильного замедления

пример использования pairs? Или чего пример?