понедельник, 25 октября 2010 г.

Производительность LuaInterface

LuaInterface - это лучший, насколько мне известно, LUA wrapper для .Net-а на сегодняшний день. Конечно, нет предела совершенству, но оперировать враппером действительно очень просто, да и возможности завораживают. Кто не читал, очень рекомендую ознакомиться с документацией, доступной на сайте проекта LuaInterface.

Однако, с точки зрения производительности, всё далеко не так радужно. В этом посте я приведу замеры производительности и использования памяти для библиотеки 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

Комментариев нет:

Отправить комментарий

Внимание! Реклама и прочий спам будут беспощадно удаляться.