-
Вчера занялся внедрением скриптового движка в продукт. Остановился на Lua, как мощном и гибком движке. Сделал классовую обертку(может кому понадобится?). Покажите на ошибки, где накосячил? unit uLua;
interface
uses lua, lualib, lauxlib,
Dialogs;
type
TLUAExternMethod = record
Name:string;
Address:lua_CFunction;
end;
TLUAParser = class
protected
FLUAState:Pointer;
FExternMethods:array of TLUAExternMethod;
Procedure LuaInit;
Procedure LuaClose;
Procedure LuaUpdateMethods;
Procedure LuaAddMethod(const MethodName:string; Method:lua_CFunction);
public
Constructor Create();
Destructor Destroy(); override;
Procedure Clear();
Procedure LoadFromMemory(const Text:string);
Function Call(const ProcName:string; Params:array of const):boolean;
Function CallResultInt(const ProcName:string; Params:array of const):integer;
Function CallResultDoudble(const ProcName:string; Params:array of const):double;
Function CallResultString(const ProcName:string; Params:array of const):string;
Procedure AddMethod(const MethodName:string; Method:lua_CFunction);
end;
implementation
procedure TLUAParser.AddMethod(const MethodName: string; Method: lua_CFunction);
begin
SetLength(FExternMethods,Length(FExternMethods)+1);
FExternMethods[Length(FExternMethods)-1].Name:=MethodName;
FExternMethods[Length(FExternMethods)-1].Address:=Method;
LuaAddMethod(MethodName,Method);
end;
Function TLUAParser.Call(const ProcName: string; Params: array of const):boolean;
var
i:integer;
begin
Result:=true;
lua_getglobal(FLUAState, PChar(ProcName));
for i := 0 to Length(Params) - 1 do begin
case Params[i].VType of
vtExtended:lua_pushnumber(FLUAState, Params[i].vExtended^);
vtInteger:lua_pushnumber(FLUAState, Params[i].VInteger);
vtString:lua_pushstring(FLUAState, PChar(Params[i].VString));
vtPointer:lua_pushnumber(FLUAState, Params[i].VInteger);
end;
end;
if (lua_pcall(FLUAState, Length(Params), 0, 0) <> 0) then begin
ShowMessage('error running function '+ProcName+': '+lua_tostring(FLUAState, -1));
lua_pop(FLUAState, 1);
Result:=false;
end;
end;
function TLUAParser.CallResultDoudble(const ProcName: string;
Params: array of const): double;
begin
if Call(ProcName,Params) then begin
Result:=lua_tonumber(FLUAState,-1);
lua_pop(FLUAState, 1);
end;
end;
function TLUAParser.CallResultInt(const ProcName: string;
Params: array of const): integer;
begin
if Call(ProcName,Params) then begin
Result:=Round(lua_tonumber(FLUAState,-1));
lua_pop(FLUAState, 1);
end;
end;
function TLUAParser.CallResultString(const ProcName: string;
Params: array of const): string;
begin
if Call(ProcName,Params) then begin
Result:=lua_tostring(FLUAState,-1);
lua_pop(FLUAState, 1);
end;
end;
procedure TLUAParser.Clear;
begin
LuaClose();
LuaInit();
LuaUpdateMethods;
end;
constructor TLUAParser.Create;
begin
LuaInit();
end;
destructor TLUAParser.Destroy;
begin
LuaClose();
inherited;
end;
procedure TLUAParser.LoadFromMemory(const Text: string);
var
error:integer;
begin
error:=luaL_loadbuffer(FLUAState, PChar(Text), length(Text), 'Chunk');
if error=0 then
error:=lua_pcall(FLUAState, 0, 0, 0);
if (error<>0) then begin
ShowMessage(lua_tostring(FLUAState, -1));
lua_pop(FLUAState, 1);
end
end;
procedure TLUAParser.LuaAddMethod(const MethodName: string;
Method: lua_CFunction);
begin
lua_pushcfunction(FLUAState, Method);
lua_setglobal(FLUAState, PChar(MethodName));
end;
procedure TLUAParser.LuaClose;
begin
lua_close(FLUAState);
end;
procedure TLUAParser.LuaInit;
begin
FLUAState:=lua_open();
luaopen_base(FLUAState);
luaopen_table(FLUAState);
luaopen_string(FLUAState);
luaopen_math(FLUAState);
end;
procedure TLUAParser.LuaUpdateMethods;
var
i:integer;
begin
for I := 0 to Length(FExternMethods) - 1 do
LuaAddMethod(FExternMethods[i].Name,FExternMethods[i].Address);
end;
end.
-
> CallResultDoudble
ненравится мне этот тип :)
-
LoadFromStream/SaveToStream где?
-
> [1] Palladin © (11.12.08 09:01)
мда уж... опечатался. :))
> [2] wicked © (11.12.08 12:10)
а зачем? Из текстовой строки грузит. а как эту строки доставали - ваше дело. Меньше привязки к юнитам.
-
> Меньше привязки к юнитам.
тогда уж убери Dialogs и ShowMessage а вместо ShowMessage('error running function '+ProcName+': '+lua_tostring(FLUAState, -1)); вызывай событие if Assigned(FOnError) then FOnError('error running function '+ProcName+': '+lua_tostring(FLUAState, -1));
-
> @!!ex © (11.12.08 13:50) [3]
Ну, поток - это более высокий уровень абстракции, чем строка. Реюзабельность класса от этого повысится.
-
А как поначалу LUA оскоблял. Теперь заценил вот :-)
> Реюзабельность класса от этого повысится.
Тогда уже сразу абстракный Stream вешать, можно и с нета и из архива напрямую качать.
-
> tesseract © (11.12.08 15:25) [6]
Конечно абстрактный!
-
TStream и имелся в виду на что-либо выше по иерархии завязываться нет смысла
-
> [4] clickmaker © (11.12.08 13:56)
Спасибо.
> [5] Alkid (11.12.08 14:50)
В данном случае код из моего проекта, котоырй сейчас делаю. А в нем Стримы вообще не используются...
> [6] tesseract © (11.12.08 15:25)
Не ругал я Lua никогда! :) В студию мои ругательства в сторону Lua! говорил, что не имеет смысла использовать в своих проектах, потому что использование чужого кода не учит писать свой.
-
> > @!!ex © (10.12.08 17:56)
Ты ж вроде на С++ перешёл?
-
> [10] Городской Шаман (12.12.08 00:24)
Есть на С++ проекты. Но это не отменило Дельфи проектов. :)
-
procedure TLUAParser.AddMethod(const MethodName: string; Method: lua_CFunction); begin SetLength(FExternMethods,Length(FExternMethods)+1); FExternMethods[Length(FExternMethods)-1].Name:=MethodName; FExternMethods[Length(FExternMethods)-1].Address:=Method; LuaAddMethod(MethodName,Method); end;
За такое перераспределение памяти уволить без выходного пособия.
-
> [12] VMcL © (12.12.08 11:43)
Ваши предложения?
-
Насколько я вижу, добавление элементов в C++ vector через push_back также дефрагментирует память. Разве есть другие варианты? List чтоли делать? Или выделять сразу блоками? Эти способы тоже не панацея.
-
TStringList
-
> SetLength(FExternMethods,Length(FExternMethods)+1); > FExternMethods[Length(FExternMethods)-1].Name:=MethodName; > > FExternMethods[Length(FExternMethods)-1].Address:=Method;
Len := Length(FExternMethods) + 1; SetLength(FExternMethods,Len); Dec(Len, 2); FExternMethods[Len].Name:=MethodName; FExternMethods[Len].Address:=Method;
на самом деле, можно не каждый раз увеличивать массив на 1, а сразу задать некую длину, и если при добавлении она превышена, перераспределить заново на некую дельту. По аналогии с Capacity в TList
-
> [15] Palladin © (12.12.08 12:34)
Еще один "левый" класс. Ради чего? И разве он также не делает realloc?
-
> Dec(Len, 2);
Dec(Len), конечно же )
-
> [16] clickmaker © (12.12.08 12:35)
Я так и делаю, в местах где данные меняются часто и критично по скорости. Здесь скорость не критична, и данные меняются только один раз при инициализации.
-
> И разве он также не делает realloc?
не на каждое добавление см. Capacity
-
> @!!ex © (12.12.08 12:35) [17]
Ни TList ни TStringList не работают с дин. массивами, у них своя схема. Как раз блоками.
-
> [20] clickmaker © (12.12.08 12:37)
Я видел как сделан TList. :) В [14] об этом написал.
> Или выделять сразу блоками?
Смысла не вижу в данном случае.
-
Ну на самом деле, все зависит от ожидаемого количествоа этих Extern методов. Если не больше 100, можно сразу размерность выделить 100. И не нагружать менеджер Reallocaми
-
> VMcL © (12.12.08 11:43) [12]
Это те 90% кода которые влияют на 1% производительности. Так что конструкция вполне легитимная, сам так делаю. Данный код при регистрации всех функций не будет на современном процессоре и 0.1 мс забирать.
Так что я против premature optimization, которая приводит к шаманской работе программ - то она работает, то нет.
-
> Palladin © (12.12.08 12:41) [23]
Угу, только при подобных оптимизация получится модуль с производительность выше на 5-10% который будет влиять на 0.0001% производительности программы, с читабельностью кода стремящейся к 0%.
Сам говорю по опыту, тоже пробовал ловить проценты производительности там где не надо. Если так уж важна производительность то уд лучше перевести проект на C++ с нормальным компилятором, а не на тормознутом Ынтерпрайзе Delphi. Даже перекомпиляция на современном FPC увеличивает часто производительность в 300%.
Это конечно хорошо для того, кто хочет завязать проект на себя, но плохо для заказчика.
-
> с читабельностью кода стремящейся к 0%.
Хм, а как повредит единоразовое определение размера дин. массива на читабельность кода?
-
> лучше перевести проект на C++ с нормальным компилятором, > а не на тормознутом Ынтерпрайзе Delphi
Зависит от критерия "нормальности". Для меня, компилятор, собирающий среднего размера проект минутами, нормальным не является :). Но, на вкус и цвет... > Сделал классовую обертку(может кому понадобится?).
Мне понадобилось, давно собирался посмотреть, что за зверь такой этот lua, а тут повод появился. Thanks :). Взял на себя смелость внести некоторые изменения в приведенный модуль: [*] Незначительные изменения для совместимости с D2009 (экспериментировал на ней). В частности, string -> AnsiString, там где нужно. [+] Добавил "Adapter" делфевых объектов. В двух словах, теперь можно легко написать функцию, которая будет возвращать указатель на объект вовнутрь lua в виде user_data. Операции над ним перекрыты с помощью т.н. метатаблиц. Луа скрипт выглядит так: local obj = trygetobject()
local name = obj.Name
obj.Name = "I was "..name.." some day, but i got NAME now!"
for i=1,10 do
obj.Size = obj.Size + 1
end и он рулит объектом, описанным так:
TMyObj = class(TObject)
private
FName: string;
FSize: Integer;
procedure SetName(const Value: string);
procedure SetSize(const Value: Integer);
published
property Size: Integer read FSize write SetSize;
property Name: string read FName write SetName;
constructor Create; virtual;
end;
Если у кого-нибудь есть идеи, как разрулить вызов методов из lua кода, был бы счастлив их услышать. Да, измененный модуль (с примером использования) лежит тут: http://slil.ru/26429923
-
Забыл добавить, размер архива по ссылке 174 Кб.
-
>>Городской Шаман (12.12.08 13:58) [24]
У каждого свои тараканы. Мои позволяют, единожды написав и протестировав код, возвращаться к нему только для добавления новой функциональности.
-
> Palladin © (12.12.08 14:12) [26] > > > > с читабельностью кода стремящейся к 0%. > > Хм, а как повредит единоразовое определение размера дин. > массива на читабельность кода?
Тем что в данном случае указатели на обработчики lua функций добавляются в динамике и для разных проблемных областей там может быть разное количество, а здесь нужно шаманить со статикой и пользователю компонента знать как он устроен внутри
Кроме того если Delphi уж берёт на "магию компилятора" динамические массивы то и компилятор должен заботится и о выделении памяти блоками под него.
Во множестве реализаций std::vector выделяется память для динамического массива блоками определенной длинны и именно разработчик библиотеки берет на себя ответственность за распределение памяти внутри.
А если компилятор при вызове Setlength(,+1) каждый раз перераспределяет память, то это кривой компилятор.
-
> А если компилятор при вызове Setlength(,+1) каждый раз перераспределяет > память, то это кривой компилятор.
Компилятор ничего не перераспределяет, он всего-лишь вставляет туда вызов соответствующей функции менеджера памяти. А менеджер памяти, если я правильно помню, можно заменить. Вроде как, именно это делал FastMM, недавно мелькало обсуждение.
В общем-то, спор ни о чем. Смысла оптимизировать при количестве добавляемых функций порядка 10-1000 по-видимому нет (добавление единократное, так как функции удаления из списка нет ^_^). С другой стороны, наличие такой оптимизации, тоже ничем не помешает (сложность кода повысится уж очень незначительно, говорить о нечитабельности или большой вероятности допустить там ошибку - преувеличение). Так что, пускай каждый и дальше делает, как ему нравится.
-
> VMcL © (12.12.08 14:38) [29] > > >>Городской Шаман (12.12.08 13:58) [24] > > У каждого свои тараканы. > Мои позволяют, единожды написав и протестировав код, возвращаться > к нему только для добавления новой функциональности.
Тогда зачем нам Delphi давайте бухгалтерские программы писать сразу на assembler с использованием SSE и CUDA для повышения производительности.
-
> Городской Шаман (12.12.08 14:59) [32]
Во всем есть здравый смысл. Давай рассмотрим конкретно эту ситуацию.
1. Это библиотечный кот. Поэтому для данного кота вопрос оптимизации имеет большую роль в силу так называемого экспоненциального взрыва.
2. Скорость работы алгоритма зависит от длины массива. Предположим достаточно типичную ситуацию, что нам надо добавить 10 000 методов. В самом печальном случае на это потребуется примерно столько же релокаций памяти плюс копирование. Опять, отбрасывая копейки, средний размер массива 40 000 * 0.5 байт = 20 000 байт, умножая на 10 000 релокаций получаем копирование 200 Mb. Конечно, менеджер памяти у Delphi хороший, и не всякая релокация будет связана с копированием. В .NET это может оказаться более фатальным. Если там не предусмотрено специальных оптимизаций, то может понадобится 10 000 выделений памяти, несколько запусков сборщика мусора, а также копирование всего заявленного объема.
3. Если в данном случае использовать TStringList, код станет и короче, и понятнее. Но даже если и самому ввести дополнительную переменную и добавить условие, то это не никоим образом не сделает программу шаманской.
-
> [33] Mystic © (12.12.08 15:37)
Lua скрипт с 10 000 глобальных методов сдохнет сам по себе, независимо от реаллока.
-
А чего ему сдыхать? Никто не говорит о том, что все эти методы будут вызываться.
-
> [35] Mystic © (12.12.08 15:53)
Обращение к функции идет через вызов метода lua_getglobal. Это перебор всех глобальных значений с сравнением строк. Для каждого вызова lua_getglobal в среднем нужно 5000 переборов. конечно там не используется явный перебор и явное сравнение строк, что позволяет чуть ускорить это дело. Но суть не меняется. Такое количество глобальных значений будет зверско тормозить. Тем более, что в глобальном пространстве хрянятся не только внешние функции, но и множество внутренних данных, которые работают через тот-же lua_getglobal. Так что это будет тормозить не только при вызове функций С из луа, но и при внутренних вызовах функций и переменных.
Опять же пример оторван от жизни. 10 000 внешних функций... Зачем? ВСе взаимодействие обычно происходит через два десятка функций. Как правило при реализации стараются избежать частого вызова lua функций из кода и наоборот.
Оптимизацию такого плана считаю глупой. Оптимизировать надо там, где тормозит. Или где, хотя бы теоретически, может тормозить. ЗДесь не может тормозить даже теоретически. Потому что при достижении большого количества методов возникнут тормоза в других точках, и значительно более серьезные.
-
> Mystic © (12.12.08 15:37) [33]
По поводу (2) тогда стоит стоит добавить отдельный метод AddMethodList(array of ...) который и будет добавлять уже сформированный массив у "клиентского кода" из 10000 методов.
И вся проблема. Но второй метод если у будет использоваться, то только в 0.0001% случаев.
-
> Mystic © (12.12.08 15:37) [33]
С другой стороны - 10000 методов, да в WinApi поменьше функций будет.
Если 10000 методов то нужно не lua, а Java + Rhino JavaScript + мейнфрейм за 10000000$.
-
> Городской Шаман (12.12.08 16:15) [37]
Можно добавить и такой метод. Но если изначально размер списка методов установить равных 128, а при исчерпании лимита увеличивать размер ровно в два раза, то нам понадобится семь релокаций, при этом памяти будет скопировано ~ 100 кб, что всего в два с половиной раза больше первоначального размера списка. Разницы между твоим оптимизированным методом AddMethodList и добавлением методов в цикле никто и не заметит. Тем более, что этот массив, который передается в качестве аргумента, также надо будет сформировать. Очень может быть, что он читается из файла, и нам заранее неизвестно сколько в нем элементов. История повторяется :)
> Городской Шаман (12.12.08 16:18) [38]
Конечно, если писать совсем не задумываясь об оптимизации, то никаких денег не хватит :) Вопрос еще и в том, что всякий раз, когда мне нужно добавить элемент к массиву, мне надо думать, к чему это приведет. Не проще ли забыть об этим потенциальных граблях и приучить себя писать дополнительные пару строк кода? А в случае TStringList и вовсе удалить пару строк. Ведь обходится большинство потенциальных проблем, а усилий никаких.
-
> [39] Mystic © (12.12.08 16:42)
Приведите код с TStringList, сравним читабельность. Как уже говорилось выше - выделение блоками применятся(лично мной) там, где это актуально. Почему ЗДЕСЬ не актауально, уже объяснил.
-
> Приведите код с TStringList, сравним читабельность.
добавление sl.Add(Name,TObject(Address));
обращение
procedure TLUAParser.LuaUpdateMethods; var i:integer; begin for I := 0 to Length(FExternMethods) - 1 do LuaAddMethod(sl[i],lua_CFunction(sl.Objects[i])); end;
ку ?
-
Автор, вроде как, пишет на BDS, а там (начиная с 2006-го) проблема решена в корне - сам менеджер памяти (FastMM) перевыделяет память блоками, на манер TList. О чём, собственно, уже писали в [31]. Хотя лично я, чтобы не зависеть от версий и менеджеров, предпочитаю самописную "шаманскую" функцию перевыделения.
-
> Sapersky (12.12.08 16:59) [42]
Смотреть надо, какой минимальный размер блока, какой между ними шаг (постоянный/экспоненциальный), алгоритм перераспределения...
-
> Mystic © (12.12.08 16:42) [39]
А не проще ли поставить нормальный менеджер памяти, который об этом позаботится автоматически?
FastMM - нет проблем! :)
-
> Mystic © (12.12.08 17:27) [43] > > > Sapersky (12.12.08 16:59) [42] > > Смотреть надо, какой минимальный размер блока, какой между > ними шаг (постоянный/экспоненциальный), алгоритм перераспределения.
Угу написать имитационную программу в matlab и на ядро системы затратить 15 лет (Minix3), когда Linux, не говоря уже о тупой кривой и глюкавой винде (вспомним работоспособность Win95) вовсю господствуют на рынке.
-
> вспомним работоспособность Win95
лучше 3.1
-
Смотреть надо, какой минимальный размер блока, какой между ними шаг (постоянный/экспоненциальный), алгоритм перераспределения...Алгоритм перевыделения в общем похож на TList.Grow. Для маленьких блоков в 2 раза (+ ещё какой-то довесок), для средних и больших - на 25%. Правда, какой конкретно размер у этих больших-маленьких - не смотрел. Но наверное уж подобран оптимальный. {This pointer is being reallocated to a larger block and therefore it is logical to assume that it may be enlarged again. Since reallocations are expensive, there is a minimum upsize percentage to avoid unnecessary future move operations.} {Must grow with at least 100% + x bytes} LNewAllocSize := LOldAvailableSize * 2 + SmallBlockUpsizeAdder; Для "средних" блоков: {Couldn't upsize in place. Grab a new block and move the data across: If we have to reallocate and move medium blocks, we grow by at least 25%} LMinimumUpsize := LOldAvailableSize + (LOldAvailableSize shr 2); Для "больших" тоже 25%. ( http://pda.delphimaster.net/?id=1226920476&n=0 )
-
> Sapersky (12.12.08 16:59) [42] > > Автор, вроде как, пишет на BDS, а там (начиная с 2006-го)
Так ничего не мешает даже к Delphi5 подключить FastMM опционально. Или на Google ссылку не нашли?
Извините, но когда мне понадобится оптимизация, а начну часть кода писать на assembler, там будет гораздо более эффективное распределение.
-
> Sapersky (12.12.08 17:42) [47]
У динамических массивов есть такая замечательная особенность как управление их жизнью со стороны компилятора, по типу интерфейсов в Delphi. Во многих случаях возможность не заниматься освобождением памяти облегчает жизнь и избавляет от глюков.
Поэтому и Java и C# пользуются популярностью, проигрывая нативным языкам во всем остальном. Повисший сервер это очень плохо, а на C++ или Delphi он до такой стабильности вылизывается годами.
-
> Sapersky (12.12.08 17:42) [47]
Да, я уже посмотрел. Все равно в мозгу мало ячеек памяти чтобы помнить что, где и как реализовано. Что опасно, а что нет. Привычка со старых времен.
-
Охрененный такой косячек в коде... lua_pop(FLUAState, 1); должно быть lua_pop(FLUAState, -1); Ну и соответственно где другие цифры, все равно - должен быть.
-
Еще косяк. pcall второй аргумент - это количество результатов. Он в моем коде всегда 0. Это не правильно. вернее для процедур это правильно. А вот функции должны указывать количество результатов, иначе нифига работать не будет.
-
> Сделал классовую обертку(может кому понадобится?).
Э-э-э вроде ж ketmar делал? Хотя, я чессно говоря не особо вник.
|