LINUX.ORG.RU

Использование bytecode lua 5.2 в С++

 ,


1

4

Пытаюсь найти ответ вот на такой вопрос.

Хочу использовать в С++ возможности lua (5.2). Предполагается что будет много скриптов и они должны будут выполять много простой логики. Притом все это дело в разных потоках паралельно и не связано между собой.

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

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

До версии 5.2 было возможно сделать что то вроде этого

  • luaL_loadfile
  • lua_dump -> to file
  • luaL_loadfile

По крайней мере можно было избавится от промежуточной компиляции. Но оставалось повтороное чтение из файла.

С версии 5.2 luaL_loadfile вообще не умеет загружать байт код. (если я правильно понял они запретили этот функционал).

Возможноли решить поставленную задачу средствами lua? Если нет, то что можно использовать вместо lua?


luaL_loadfilex — в мануале все нормально. Да и в листе никогда такого не пролетало. Я что-то пропустил?

arturpub ★★
()

А вообще лучше так:

// загрузка
if (luaL_loadfile(L, filename)) { shit... }
int module1_r = luaL_ref(L, LUA_REGISTRYINDEX);

// использование
lua_rawgeti(L, LUA_REGISTRYINDEX, module1_r);
lua_call(L, 0, 0);

// выгрузка
luaL_unref(L, LUA_REGISTRYINDEX, module1_r);
module1_r = LUA_REFNIL;
Еще можно запихнуть в реестр таблицу (также, через luaL_ref или просто по уникальному имени / lightuserdata), и в нее пихать ключ = имя файла, значение = скомпиленная функция.

Оффлайн-прекомпилировать можно, но учти, что байткод небезопасен, т.к. валидация его в общем виде нетривиальна, и реализовывать ее никто не собирался. Уронить твою прогу сегфолтом или заэксплойтить, имея доступ к кешу байткода — раз плюнуть. Кешируй на лету лучше, так второй раз даже с диска поднимать не придется.

arturpub ★★
()

Ну и немного оффтопика: нахрен тебе 5.2? Весь продакшн застрял на 5.1 и никуда не собирается, jit только для 5.1, 90% плюшек 5.2 реализуется небольшой луа-библиотекой, поскольку изменения в стандартной библиотеке, а не в ядре. Из интересного там только yield across everything с костылями в виде lua_callk, который всю жизнь был в luajit'е.

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

Возможно в релизе 5.2 они вернули эту возможность, просто в интернете при гуглении на тему lua load bytecode первой ссылкой получаешь это

http://lua-users.org/lists/lua-l/2010-05/msg00549.html

это как раз пререлиз 5.2

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

Я просто взял последний релиз, увидел что он от 2010 года и подумал что нет смысла брать старее.

Какой профит может дать jit при использовании lua из С++ (я не знаю что такое jit)

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

А есть какие то ограничения на размер реестра? Ну например 1-2к скриптов предкомпиленных там можно держать?

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

А вот идею на счет «кешируй на лету» не уловил вообще.

Для того что бы было понятней почему так много скриптов и почему их хочется хранить в уже подготовленном виде.

Есть игровой сервер, логика выносится в скрипты. К примеру у нас есть 30 видов домиков, каждый имеет по 10 левелов. Каждый левел имеет скрипт на апгрейд и на инстант апгрейд. Это уже 600 скриптов. каждый из которых в принципе просто строк по 20. Есть вероятность что загрузка скрипта будет занимать больше времени чем его выполнение.

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

Это было не о том.

Луа версии несовместимы между собой внутри второй цифры, только в третьей — там багфиксы без серьезных изменений. 5.1 это мейнстрим. Ветка 5.2 не сказать, что экспериментальная, но и не сказать, что все рвутся на нее перейти. JIT — just in time компилятор, который отслеживает горячие места (читай циклы) и компилирует их в нативный код. Реализован для основных платформ и активно разрабатывается, кроме ios, т.к. там jit запрещен правилами аппстора. Тем не менее LuaJIT 2.0.x (5.1) даже в режиме чистого интерпретатора существенно шустрее стандартного 5.1.

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

Какой профит может дать jit при использовании lua из С++

Как мимнмум, у LuaJIT есть свой собственный формат байт-кода, который платформеннонезависим (в отличие от) и более компактен. А по скорости LuaJIT заруливает референсную реализацию даже в режиме интерпретатора, правда размер интерпретатора больше, и платформ поддерживается весьма ограниченное количество

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

Реестр это просто луа-таблица, невидимая со стороны скрипта. Там севая часть может хранить свои луа-данные. Целые индексы зарезервированы под т.н. ref'ы (luaL_ref), строковые часто используются для кеширования метатаблиц (luaL_newmetatable). Размер не ограничен, т.к. обычная таблица. В памяти это не должно много занимать, исходя из 10 опкодов на строку, а это заранее дофига, миллион строк займет 10-30 мегабайт.

Кешировать на лету — я имел ввиду загружать из файлов в реестр и в следующий раз брать сразу оттуда.

static int modules_r = LUA_REFNIL;

int
loadmodule(lua_State *L, const char *filename)
{
    if (modules_r == LUA_REFNIL) {
        lua_createtable(L, 0, 0);
        modules_r = luaL_ref(L, LUA_REGISTRYINDEX);
    }
    lua_rawgeti(L, LUA_REGISTRYINDEX, modules_r);
    lua_getfield(L, filename);
    if (lua_isnil(L, -1)) {
        lua_pop(L, 1); // nil
        if (luaL_loadfile(L, filename)) {
            fprintf(stderr, "%s", lua_tostring(L, -1));
            lua_remove(L, -2); // modules
            return 1; // error still at top
        }
        else {
            lua_pushvalue(L, -1);
            lua_setfield(L, -3, filename);
        }
    }
    lua_remove(L, -2); // modules
    return 0; // module at top
}
arturpub ★★
()
Ответ на: комментарий от Cupper

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

Все правильно предполагается.

arturpub ★★
()

Загружаем скрипт используя luaL_loadbuffer, потом просто дергаем функцию внутри LUA по имени. Т.е. один раз загрузили, потом 100000 раз дернули. Перегружаем только если файл поменялся.

vromanov ★★
()

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

Кстати вот это не получится. На каждый с++thread надо будет создавать свой lua_State, причем разные стейты не могут обмениваться данными, т.к. ну ты понел. Умножай память на количество тредов. Шарить единый стейт между тредами можно только эксклюзивно обычным мутексом — захватил, поковырялся, расхватил, свободная касса. Если ты делал ставку на coroutines, то это не cooperative, а preemptive multitasking.

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

не думаю. Обычно это выглядит так, пользователь хочет апгрейднуть здание. Мы на сервере по конфигам ищем для этого здания соответствующий конфиг с левелом. Из этого конфига получаем имя скприта который нужно выполнить для этогт левела/здания. Выполняем скрипт (он что то там проверяет, забирает, делают почую логику). Если скрипт выполнился значит можно перейти непосредственно к изменению уровня у здания.

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

Спасибо, с виду это как раз то что нужно.

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

Как раз планировалось по виртуалке не то что на поток, на каждый запрос. Т.к. работаем асинхронно в режиме запрос (от клиета) ответ (от сервера).

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

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

Если скриптов много, то стоимость создания стейта будет большая (она и так думаю немалая). Можно конечно использовать динамический пул, возвращая в него освободившиеся стейты и собирая по к-л критерию излишки после пиковых нагрузок. Без живых тестов непонятно, во что ты упрешься, может серверный io все сожрет, и хватит просто небольшого Х'а стейтов на железку.

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

А как связано создание стейта и объем загруженных в реестр скриптов ? Они же не копруются в каждый стейт при его создании?

Как правило Один запрос - одна виртуалка - один (может пару, но не более) скриптов.

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

У каждого стейта свое пространство данных, никак не шарится. Собственно поэтому это стейтом называется. Можно конечно все держать в одном главном, но из него придется делать дамп и лоад в другой — то же что из внешнего файла. Теоретически это быстрее, чем из файла, т.к. нет расходов на ио, но это не общая память все равно.

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

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

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

Плюс профиты от jit'а исчезнут, если стейты каждый раз убивать — компиляция дело недешевое.

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

Ну и немного оффтопика: нахрен тебе 5.2?

Сравнительные тесты показывают, что она намного шустрее.

Cupper, а может не заморачиваться? Предварительная оптимизация — сам знаешь. Может вначале сделать без кэширования байткода, а потом профилировать — и если окажется, что затык именно в этом, тогда уже его и добавлять?

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

Мы делали такие тесты.. Т.е. имеет ли смысл грузить скрипт каждый раз перед выполнением. Разница была на порядки.

Если интересно, есть работающий пример (не OpenSource). Лежит тут - freepcrf.com в виде образа виртуалки. Там можно вызвать lua скрипт HTTP запросом. В приложении и в доке называется GRAPI. (Generic Request API). Также там дергается луа при обраотке запросов по diameter. Держит несколько тысяч запросов в секунду.

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

Круто. Посмотрим.

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

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

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

Мы не кешировадли байткод. Просто для каждого скрипта и процесса заводится свой lua_State, в него грузится скрипт. После этого много раз запускается функция. Т.к. скрипт меняется раз в неделю, а за неделю у нас скрипт может быть выполен миллиард раз, то временем загрузки можно просто пренебречь. Естественно загружать скрипт перед каждый запросом - сильно медленее, чем загрузить его один раз. Еще из хитростей - мы вручную дергаем GC каждые 1000 запусков. Если дергать реже - скрипт начинает тормозить. Видать время выделения памяти линейно зависит от количесва объектов в памяти.

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

Потратив некоторое время на изучения Lua. Я понял что единственная возможная концепция это иметь по отдельной state машине на каждое выполнение.

Т.е. на первом шаге, загружить каждый скрипт в эталонную state машину. А далее при каждом выполнении брать копию эталонки (надеюсь это возможно) и работать с ней.

Далее я переключился на следующий насущный вопрос. Реализация связи между C++ кодом и Lua. Связать отдельные функции или даже сделать C++ классы доступными в Lua не проблема.

Единственно чего я пока не понял, а можно ли работать в Lua с объектами созданными в C++. Я прекрасно понял что я могу создать объект класса в Lua. А могу ли я туда просто передать указатель на объект и заставить Lua с ним работать c использованием тех же metatable?

Объект может состоять из кучи подобъектов и менеджить кучу динамической памяти и прочих(некопируемых) ресурсов. И нет ни какой возможности делать deep copy все этого добра из С++ memory в Lua memory, и наоборот тоже.

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

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

Теперь интерестно следующее. Вы говорили

Просто для каждого скрипта и процесса заводится свой lua_State

а если один lua_State нужно одновременно выполнять из разных потоков? Понятно дело так нельзя. Хотелось бы узнать можно ли делать клон из заранее подготовленного стейта (с загруженным скриптом, таблицами для классов и функций). Пока вырисовывается что нельзя. И это печально. Непонятно почему нельзя.

Есть у кого идеи на этот счет?

Cupper
() автор топика
Вы не можете добавлять комментарии в эту тему. Тема перемещена в архив.