Однако, с точки зрения производительности, всё далеко не так радужно. В этом посте я приведу замеры производительности и использования памяти для библиотеки LuaInterface.
К примеру, у нас есть вот такой код:
Foo foo = new Foo(); foo.Bar = 10;
И нам его нужно отобразить в Lua (чтобы там тоже был объект foo с полем Bar). Самое простое, что приходит в голову:
using (Lua l = new Lua()) { l.DoString("foo = { Bar = 10 }"); }
В достаточно большом проекте таких объектов может быть очень много, к тому же объекты могут быть вложенными. В тесте я использую миллион итераций, с учетом того, что в обычном объекте намного больше одного поля, думаю, это весьма реалистичный вариант. Замеряем!
using (Lua l = new Lua()) { Measure("LuaInterface Lua.DoString", 1000000, () => { l.DoString("foo = { Bar = 10 }"); }); }
Measure - это крохотная функция, которая замеряет время выполнения делегата указанное число раз:
delegate void MeasureDelegate(); static void Measure(string name, int count, MeasureDelegate code) { DateTime timestart; DateTime timefinish; TimeSpan timespan; timestart = DateTime.Now; for (int i = 0; i < count; i++) code.DynamicInvoke(new object[] { }); timefinish = DateTime.Now; timespan = timefinish - timestart; Debug.WriteLine("{0} => {1}", name, timespan); }
В данном случае в результате её выполнения, получаем:
LuaInterface Lua.DoString => 00:01:02.3906250Ох ничего себе... Минута чтобы отобразить в Lua, минута чтобы получить обратно... На самом деле, как выяснилось, DoString вообще тормозит безбожно. Ради интереса я запустил его миллион раз, вместо скрипта передав пробел:
using (Lua l = new Lua()) { Measure("LuaInterface Lua.DoString", 1000000, () => { l.DoString(" "); }); }Результат:
LuaInterface Lua.DoString => 00:00:27.4062500Ну и еще один тест:
using (Lua l = new Lua()) { l.DoString("foo = {}"); Measure("LuaInterface Lua.DoString", 1000000, () => { l.DoString("foo.Bar = 10"); }); }
Результат:
LuaInterface Lua.DoString => 00:00:53.0781250Вывод: в операциях отображения объектов в/из Lua DoString лучше не использовать.
Но тогда что? В LuaInterface есть индексатор для глобальных объектов, и класс LuaTable для работы с таблицами (это Lua-аналог структур и списков).
using (Lua l = new Lua()) { l.NewTable("foo"); Measure("LuaInterface LuaTable", 1000000, () => { ((LuaTable)l["foo"])["Bar"] = 10; }); }
Результат:
LuaInterface LuaTable => 00:00:16.7500000Намного лучше! Но есть один БОЛЬШОЙ минус. Оказывается, такой вариант порождает утечки памяти. Почему это происходит, я не знаю, скорее всего в будущих версиях всё будет исправлено. В реальном проекте из-за этой утечки в процессе генерации мира (250к объектов) программа сожрала почти 1.2Гб оперативки. А мое тестовое приложение, обычно занимающее в памяти около 8Мб, разрослось аж до 25.
Что остается?! Я не нашел иного варианта, кроме как использовать низкоуровневый враппер LuaDLL из того же самого LuaInterface. В этом варианте, кстати, есть определенные бенефиты - появляется возможность оптимизировать вызовы lua, и достичь наиболее высокой производительности.
Вот что получилось у меня для финального теста:
IntPtr l = LuaDLL.luaL_newstate(); LuaDLL.lua_newtable(l); LuaDLL.lua_setglobal(l, "foo"); LuaDLL.lua_getglobal(l, "foo"); Measure("LuaDLL.lua_settable", 1000000, () => { LuaDLL.lua_pushstring(l, "Bar"); LuaDLL.lua_pushnumber(l, 10); LuaDLL.lua_settable(l, -3); }); LuaDLL.lua_close(l);
Результат очень закономерный:
LuaDLL.lua_settable => 00:00:07.2656250
Я считаю, очень приличная производительность, и, заметьте, в этом случае никаких утечек памяти!
P.S. Тестировалось на LuaInterface 2.0.3
Комментариев нет:
Отправить комментарий
Внимание! Реклама и прочий спам будут беспощадно удаляться.
Примечание. Отправлять комментарии могут только участники этого блога.