Информация Оптимизация Lua сценариев

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

Я покажу 6 примеров с оптимизацией, с которым я лично сам сталкивался при написании Lua скриптов. Для тестирования буду использовать Lua 5.1.5, Lua 5.4.2 и LuaJIT 2.1.0-beta3 (с функциями Lua 5.2). Lua 5.1 добавлена к сравнению потому, что LuaJIT базируется на этой версии.

Характеристика​

1610305370888.png

Спасибо @RoffDaniel за предоставленный сервер.

Локальные переменные​

В языке существуют 2 типа переменных – глобальные и локальные. Глобальные хранятся в таблице _G (с Lua 5.2 существует еще _ENV), в свою очередь локальные – «в регистрах виртуальных машин». Многие люди, писавшие на языке Lua, рекомендуют избегать глобальные переменные, чтобы использовать локальные, так как «доступ к локальным значительно быстрее, нежели к глобальным».
34a20c82cf4d26e33565d.png

cda780fe9d35697714b31.png

c3510cbb02c1c3b8771d1.png
Как видно, действительно доступ к локальным быстрее на Lua 5.1 на 20%, и Lua 5.4 на 30%, но LuaJIT показал одинаковый результат. Объяснение данного феномена на сайте JIT-компилятора я не нашел.

Заполнение таблицы​

В Lua существует функция для записи значения в таблицу – table.insert. Но для записи в конец предпочитают использовать запись с помощью индексации, иначе говоря, a[#a + 1] или #a.
8248eda0bb60b85ad28a9.png

aa74bfa99a28d29792a7e.png

71abf2fb99d8c36a16166.png
a[i] оказался лучше всех по всем 3 результатам. a[#a + 1] показал хорошие результаты на «чистых» реализациях Lua.

Разбор таблицы​

Если вам надо разобрать таблицу только с числовыми индексами 1.., то pairs не обязательно для этого использовать. Для этого случая существует функция ipairs. Некоторые используют традиционный способ – for i = 1, #a do. Я решил добавить еще реализацию с функцией table.maxn, но данной функции нету начиная с Lua 5.2 по причине того, что функция является устаревшей, и с next. Давайте взглянем на скорость.
524f22ec9d998d812e4b7.png

9bd833942a49e13b940a5.png

14c60da768f750de708b6.png
#a оказалось лучше всех. Далее ipairs, который показал хорошие результаты в Lua 5.4 и в LuaJIT 2.1, но в Lua 5.1 почему-то показал результат хуже, чем pairs. Скорее всего, это погрешность. Хуже всех справилась реализация с next.
В LuaJIT table.maxn справилась лучше ipairs, нежели в Lua 5.1.

Конкатенация​

Мне приходилось работать с большими текстами и я, как многие «скриптеры», использовал оператор .., но этот оператор работал медленно и я пытался найти альтернативу этому оператору. И мне удалось найти – функция table.concat.
e140b878e06404427633e.png

41963e8e298831c87e02e.png

b212fa1b89fb49e99d2ef.png
Как видно по графикам, функция table.concat справилась намного лучше, чем оператор конкатенации строк.

string.char и string.byte​

Когда я делал Xor test, то я задумался, а как можно еще быстрее ускорить, кроме вышеупомянутого table.concat. Я присмотрелся к функциям string.char и string.byte и решил заменить их на таблицы.
e90a3b910419d5d522148.png

02d586d1e7b3e2eeb33be.png

7c99c9fbb09d73044627f.png
Таблица оказалась быстрее в Lua 5.1 и Lua 5.4, но для LuaJIT ничего не поменялось.

Вариативные аргументы​

Видел много сценариев, где вариативные аргументы превращали в таблицу, потом использовали дальше. Этот вариант звучит неплохо, но мы в этой статье рассматриваем код, который будет быстрее работать. Поэтому вместо таблицы лучше использовать функцию select. Для теста добавил еще функцию table.pack (Lua 5.2+).
ff14ec4452eea75aaef30.png

d683ce1c0e52a48011ef8.png

9d1cc0ea9841b87a3e23b.png
Функция select оказалась лучшим способом. Для JIT версии можно использовать table.pack, так как тоже быстро обрабатывает и на результате дает таблицу.

Выводы​

В этой статье рассмотрено 6 способов для ускорения Lua сценариев, которые я встречал с 2017 года. Конечно, не все способы помогли для LuaJIT, но все же лучше использовать их и там.
Исходные коды Lua сценариев: https://github.com/FishLakeDev/speedtest/tree/main/lua-optimization
Результаты тестов в файле формата Microsoft Excel: https://t.me/fishlakedev_files/4
Подписывайтесь на мой канал в Telegram!

Оригинальная статья: https://telegra.ph/Optimizaciya-Lua-scenariev-01-10
 

Hatiko

Известный
Проверенный
1,502
620
Как видно, действительно доступ к локальным быстрее н
Вторая хорошая причина использовать их для скомпилированных скриптов - обфускация кода после декомпиляции.
Жалко что их только макс 200 шт можно юзать, иногда мало )

Как видно по графикам, функция table.concat справилась намного лучше, чем оператор конкатенации строк.
Да, но если идёт речь для объединения элементов таблицы.
О ней кстати вообще относительно недавно узнал, 3-4 месяца назад, а то приходилось вечно костылить в несколько строк для отсеивания лишних значений.
Может ещё есть какие-то полезные функции, которые редко встречаешь...

a оказался лучше всех по всем 3 результатам. a[#a + 1] показал хорошие результаты
ля, а кто-то из умных недавно ведь писал, что insert быстрее, а оказывается данный "костыль" быстрее, но стоит ли он того. Ну если конечно задача стоит из большой таблицы или в необходимости быстродействии.
Локальные переменные с самого начала уже привык писать, как оказалось не зря ).. Сам где-то тоже начал с 2017 с изучения lua. Но несмотря на долгий период работы с луа иногда встречаешь что-то новое. Было бы неплохо увидеть какую-то статью про разнообразные функции, фичи и т.п., которые не особо часто использует народ из-за незнания., если конечно же их достаточно.
А так, почитать для общего развития полезно будет.
 
  • Вау
Реакции: whyega52

imring

Ride the Lightning
Автор темы
Всефорумный модератор
2,361
2,546
Жалко что их только макс 200 шт можно юзать, иногда мало )
вроде 60 лимит, да и можно создать другую функцию.

ля, а кто-то из умных недавно ведь писал, что insert быстрее, а оказывается данный "костыль" быстрее, но стоит ли он того
если ты используешь это для скриптов в муне, то смотри результаты luajit 2.1.0-beta3.
и ещё: https://www.blast.hk/threads/71446/

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

Rei

Известный
Друг
1,611
1,668
Да, но если идёт речь для объединения элементов таблицы.
часто ты хранишь много текста не в таблицах? или генерируешь его не в цикле.
но профитно это для реально большого количества кусков текста
Lua:
function concat(words)
    local s = {}
    for i = 1, #words do
        s[i] = words[i]
    end
    return table.concat(s)
end
Lua:
local aa, start = {}

for i = 1, 100 do
    aa[#aa+1] = 'asjfUAIsfjjasfjiA'
end

start = os.clock()
for i = 1, 100000 do
    local s = ''
    for i = 1, 100 do
        s = s .. 'asjfUAIsfjjasfjiA'
    end
end
sampfuncsLog('.. operator: '..os.clock()-start)

start = os.clock()
for i = 1, 100000 do
    local s = concat(aa)
end
sampfuncsLog('concat func: '..os.clock()-start)
Код:
.. operator:   2.7929999999997
concat func:   0.45499999999993
 

imring

Ride the Lightning
Автор темы
Всефорумный модератор
2,361
2,546
часто ты хранишь много текста не в таблицах? или генерируешь его не в цикле.
но профитно это для реально большого количества кусков текста
Lua:
function concat(words)
    local s = {}
    for i = 1, #words do
        s[i] = words[i]
    end
    return table.concat(s)
end
Lua:
local aa, start = {}

for i = 1, 100 do
    aa[#aa+1] = 'asjfUAIsfjjasfjiA'
end

start = os.clock()
for i = 1, 100000 do
    local s = ''
    for i = 1, 100 do
        s = s .. 'asjfUAIsfjjasfjiA'
    end
end
sampfuncsLog('.. operator: '..os.clock()-start)

start = os.clock()
for i = 1, 100000 do
    local s = concat(aa)
end
sampfuncsLog('concat func: '..os.clock()-start)
Код:
.. operator:   2.7929999999997
concat func:   0.45499999999993
тест не совсем корректный, ибо ты заранее создал таблицу со строками. надо инициализацию таблицы тоже внести в цикл.
и я же опубликовал скрипты, которые я тестировал: https://github.com/FishLakeDev/speedtest/blob/main/lua-optimization/concat.lua

или можно заменить в 11 строчке 'as...' на aa[i] и посмотреть что лучше - table.concat или свой цикл с ..
 
Последнее редактирование:

Rei

Известный
Друг
1,611
1,668
тест не совсем корректный, ибо ты заранее создал таблицу со строками. надо инициализацию таблицы тоже внести в цикл.
и я же опубликовал скрипты, которые я тестировал: https://github.com/FishLakeDev/speedtest/blob/main/lua-optimization/concat.lua

или можно заменить в 11 строчке 'as...' на aa[i] и посмотреть что лучше - table.concat или свой цикл с ..
а лол, я под температурой что-то напутал... у меня была какая-то своя задумка, отличная от твоего теста, но я забыл))
но по сути из-за лишнего цикла в функции получились те же результаты, что и при правильном варианте
Lua:
local start = os.clock()
for i = 1, 10000 do
    local s = ''
    for i = 1, 100 do
        s = s .. 'asjfUAIsfjjasfjiA'
    end
end
sampfuncsLog('.. operator: '..os.clock()-start)

start = os.clock()
for i = 1, 10000 do
    local s = {}
    for i = 1, 100 do
        s[i] = 'asjfUAIsfjjasfjiA'
    end
    local a = table.concat(s)
end
sampfuncsLog('concat func: '..os.clock()-start)


.. operator: 0.29000000000087
concat func: 0.046999999998661
 
  • Нравится
Реакции: ADscripts