テーブルシリアライズ高速版

前回は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ぐらいにはなりました。参考までに。