テーブルシリアライズ高速版
前回はlua上でテーブルシリアライズして文字列で返すものを作りましたが、今度はc(Delphi)版です。
この場合の利点は「速度」のみです。
テーブルのコピー(参照渡しじゃないよ)とか、VM間で値(number,string,table)の受け渡し、SaveLoadの値の書き込み、等に使えるので高速版があると幸せになれるかもしれませんよ。
今回のシリアライズは文字列ではなくメモリ上にバイナリ(TMemoryStream)で出力します。
なので、もしLuaにシリアライズ後のデータを渡すにはUserPointer型しか有りませんし、GCも勝手に解放してくれません。
アプリ側でテーブルデータをどうのこうのするって使い方が良いんじゃないかと思います。
function _BinarySerialize(vm:Plua_state; StackIndex:Integer=0):TmemoryStream; var ms : TMemoryStream; function invalue(i:Integer):Integer; var bb,tt : Byte; ii : Integer; pp : Pointer; dd : Double; ss : AnsiString; begin if (i=255)then //End Of Table begin bb := 255; ms.Write(bb,1); RESULT := 255; exit; end; tt := lua_type(vm,i); ms.Write(tt,1); case tt of LUA_TNIL: ; LUA_TBOOLEAN : begin if (lua_toboolean(vm,i))then bb:=1 else bb:=0; ms.Write(bb,sizeof(Byte)); end; LUA_TLIGHTUSERDATA : begin pp := lua_touserdata(vm,i); ms.Write(pp,sizeof(Pointer)); end; LUA_TNUMBER : begin dd := lua_tonumber(vm,i); ms.Write(dd,sizeof(double)); end; LUA_TSTRING : begin ss := lua_tostring(vm,i); ii := length(ss); ms.Write(ii,sizeof(Integer)); ms.Write(ss[1],ii); end; LUA_TTABLE : ; LUA_TFUNCTION : begin pp := Pointer(lua_tocfunction(vm,i)); ms.Write(pp,sizeof(Pointer)); end; LUA_TUSERDATA : begin pp := lua_touserdata(vm,i); ms.Write(pp,sizeof(Pointer)); end; LUA_TTHREAD : begin pp := lua_tothread(vm,i); ms.Write(pp,sizeof(Pointer)); end; end; RESULT := tt; end; procedure intable(i:Integer); var t : Integer; b : Byte; begin //テーブルはスタックの i の位置にあるとする lua_pushnil(vm); //最初のキー while (lua_next(vm,i)<>0)do begin //「キー」はインデックス-2、「値」はインデックス-1 invalue(-2); //key t := invalue(-1); //value if (t=LUA_TTABLE)then intable(lua_gettop(vm)); //「値」を除去し、「キー」は次の繰り返しのために残す lua_pop(vm,1); end; invalue(255); //End Of Table end; begin //StackTopにTableが乗っていること ms := TMemoryStream.Create; ms.SetSize(1024); //Capacity if (StackIndex<1)then StackIndex := lua_gettop(vm); if (invalue(StackIndex)=LUA_TTABLE)then intable(StackIndex); ms.SetSize(ms.Position); RESULT := ms; end; procedure _BinaryDeserialize(vm:Plua_state; ms:TmemoryStream); function invalue:Integer; var bb,tt : Byte; ii : Integer; pp : Pointer; dd : Double; ss : AnsiString; begin ms.Read(tt,1); RESULT := tt; if (tt=255)then exit; //End Of Table case tt of LUA_TNIL: ; LUA_TBOOLEAN : begin ms.Read(bb,sizeof(Byte)); if (bb=1)then lua_pushboolean(vm,TRUE) else lua_pushboolean(vm,FALSE); end; LUA_TLIGHTUSERDATA : begin ms.Read(pp,sizeof(Pointer)); lua_pushlightuserdata(vm,pp); end; LUA_TNUMBER : begin ms.Read(dd,sizeof(double)); lua_pushnumber(vm,dd); end; LUA_TSTRING : begin ms.Read(ii,sizeof(Integer)); SetLength(ss,ii); ms.Read(ss[1],ii); lua_pushstring(vm,@ss[1]); end; LUA_TTABLE : ; LUA_TFUNCTION : begin ms.Read(pp,sizeof(Pointer)); lua_pushcclosure(vm,pp,0); end; LUA_TUSERDATA : begin ms.Read(pp,sizeof(Pointer)); lua_pushlightuserdata(vm,pp); end; LUA_TTHREAD : begin ms.Read(pp,sizeof(Pointer)); //わかんね。文字列でもいれとけ ss := '(*thread)'; lua_pushstring(vm,@ss[1]); end; end; end; procedure intable; var i,t : Integer; begin // テーブル新規生成(ハンドラがスタックに積まれる) lua_newtable(vm); while(TRUE)do begin t := invalue; //key if (t=255)then break; t := invalue; //value if (t=LUA_TTABLE)then intable; lua_settable(vm,-3); //セット end; end; var t : Integer; begin //StackTopにTableを返す t := invalue; if (t=LUA_TTABLE)then intable; end;
シリアライズはテーブル以外にも、普通の値も可能です。なのでSaveLoadにも便利かなーと思います。
それぞれの使い方がちょっと難しくて、binaryserializeはPlua_stateのStackIndex番目のスタックのテーブルをシリアライズ化します。省略すると一番トップ。
binarydeserializeは、Plua_stateの一番上にテーブルか値を作って返します。なので、返値無しのprocedureになっておるかんじです。
luaバージョンと簡単に比較すると、serializeは6倍ぐらい、deserializeは3倍ぐらい、トータルで3〜4倍ぐらい速くなります。うん、あんまり早くならないね。
処理時間の9割がlua命令によるところで、やっぱりテーブルに値を入れていく際のメモリ確保&解放がボトルネックなようです。
試しに、メモリアロケータをDelphi(FastMM4)のにしてみると
function _Lua_Alloc(ud, ptr : Pointer; osize, nsize : size_t) : Pointer; cdecl; begin if (nsize=0)then begin FreeMem(ptr); RESULT := nil; end else begin ReallocMem(ptr,nsize); RESULT := ptr; end; end; 〜 //L := luaL_newstate; L := lua_newstate(_Lua_Alloc,nil);
環境によっては、2倍ほど速くなったりするかもしれませんよー。
とりあえず、うちの環境では「binaryserialize + FastMM Alloc」で、545ms→71msぐらいにはなりました。参考までに。