替代Protocol buffers 的FlatBuffers:高效利用内存的序列化函数库(Unity中测试)

2017-01-13 10:50:18来源:csdn作者:u010019717人点击

第七城市

孙广东 2015.7.4

http://www.open-open.com/lib/view/open1441004786315.html

虽然FB的反序列化超级快,但是数据大小还是蛮大的(可能的原因是不需要解析,直接访问binary buffer,所以不能对数据进行压缩),带来的传输或存储开销,可能会抵过一部分反序列化的时间。感觉上,FB的使用场景上还是很受限,在移动端可能是一个应用场景。

protocol buffer的作者后来又弄了一个Cap'n Proto ( 但是好像应用场景没有太火 )

http://google.github.io/flatbuffers/flatbuffers_support.html

提供了对 Unity游戏引擎C#的 支持!(本身 FlatBuffers 就是 移动平台移动开发 而出的!)

关于Unity的示例代码 必须要学习啊!!!!!!

http://exiin.com/blog/flatbuffers-for-unity-sample-code/

FlatBuffers 是 Google为游戏和移动应用而开发的开源、跨平台的序列化库。支持 C++, Java, C#, Go, Python and JavaScript等语言。中国有5亿智能手机,其中低端设备占多数,在 CPU 和内存都受限的情况下,能否开发出高性能且低内存占用的 Android程序决定了你的应用的用户覆盖率和留存率。

在Unity使用 FlatBuffers 作为存储格式(移动版)

翻译: http://qiita.com/akerusoft/items/8c10f8a40fee722e6d1b

Unity提供了 PlayerPrefs作为本地的数据存储, 但是速度不是很快!

下面就是一个测试使用FlatBuffers

Unity 5.3.3f1 版本

FlatBuffers 1.3.0版本

运行在 Android /IOS 测试

FlatBuffers序列化保存为二进制。

cocos2dx 游戏数据使用

还应用在Facebook上。

FlatBuffers,分为 结构定义 (架构) 和 数据

它要在服务器和客户端可用。

使用 FlatBuffers 的流程。

结构定义 自动生成的相应的 语言(java,C# ,java等)文件序列化/反序列化过程

schema language (aka IDL, Interface DefinitionLanguage)

namespace Data;

file_identifier"MYFI";

unionData
{
MonsterDataV1
}

tableRoot
{
data:Data;
}

tableMonsterDataV1
{
name:string;
hp:uint;
hitRate:float;
speed:uint;
luck:uint;
}

tableMonsterDataV2
{
name:string;
hp:uint;
hitRate:float;
speed:uint;
luck:uint;
defence:uint;
}

root_typeRoot;

来自 <http://qiita.com/akerusoft/items/8c10f8a40fee722e6d1b>

首先是表table MonsterDataV1

这假设是在 rpg 游戏中使用的怪物结构。

表table 字段声明(类型写在 后面)

字段是通过使用 内置类型 进行描述。

除了内置类型,你也可以使用自定义的类型(除了当前table)。

以下是union的数据。

这是种枚举类型,主要特征以table表作为字段。

在本例中是定义的字段 只有 MonsterDataV1

union 中的所有字段占用同一个内存存储(同一时间也只有一个字段有效)。

如果你也可以添加 MonsterDataV2 字段,但数据也只有一个存储。(听起来像一个 c 语言中的union)

基于这种分析。

"file_identifier"是写在 文件的开头的 文件 ID,您可以使用 4 ASCII 字符 (字节 4-7)。

它是可能要检查服务器发送数据,等等......

升级注意事项

应用程序或游戏,要进行数据升级,当一项新功能被增加到应用程序

但是 schema 有一定的局限性。

应该不会删除字段,如果数据已经在使用,不更改声明字段的顺序 (新数据在后面追加就行)

升级的Demo

小的是升级,将向表中添加字段。

之前的MonsterDataV1声明:

table MonsterDataV1
{
name:string;
hp:uint;
hitRate:float;
speed:uint;
luck:uint;
}

添加新的字段:

table MonsterDataV1
{
name:string;
hp:uint;
hitRate:float;
speed:uint;
luck:uint; (deprecated)
hoge1:[ubyte];
hoge2:bool;
hoge3:int;
hoge4:long;
hoge5:long; (deprecated)
hoge6:long;
hoge7:long;
hoge8:long; (deprecated)
hoge9:long;
}

union中添加新的 table类型!!!!!

unionData
{
MonsterDataV1,
MonsterDataV2
}
程序的输出

Flatc 可执行文件程序的输出。

Windows 版本是 exe 在github页上的窗体。

对于一个 c# 文件生成用下面的命令。

flatc.exe -o OUTPUT_PATH -n--gen-onefile inputfile.fbs

"OUTPUT_PATH" 请设置路径输出。

要输出一个 c# 文件参数 '-n'

"--gen-onefile"生成 cs 文件时将被输出到一个单独的文件。

您声明数据定义schema文件的名称是 "inputfile.fbs"(所以schema对文件名和后缀名美没有任何限制吧!) 。

或者直至这样写: flatc -n schemaTemplate.txt --gen-onefile

要导入到Unity的运行时文件

运行 FlatBuffers 项目将生成的Dll 导入到Unity的尝试

从官方github DL 。

http://blog.csdn.net/u010019717

在脚本文件中的序列化/反序列化

要反序列化demo github.

在 Test1 test2 序列化反序列化过程中的执行。

ButtonCreate.cs

stringpath = Path.Combine(Application.persistentDataPath, DataName.FILE_NAME);

if(File.Exists(path))
File.Delete(path);

FlatBufferBuilderbuilder = new FlatBufferBuilder(1);

intoffestData;
Data.Data dataType;

Offset<Data.MonsterDataV1>data = Data.MonsterDataV1.CreateMonsterDataV1(builder,builder.CreateString(name), hp, hitRate, speed, luck);
offestData = data.Value;
dataType = Data.Data.MonsterDataV1;

Data.Root.StartRoot(builder);
Data.Root.AddDataType(builder,dataType);
Data.Root.AddData(builder,offestData);
Offset<Data.Root> endOffset =Data.Root.EndRoot(builder);

Data.Root.FinishRootBuffer(builder,endOffset);

bytes =builder.SizedByteArray();

File.WriteAllBytes(path,bytes);

反序列化过程:

ButtonLoad.cs

stringpath = Path.Combine(Application.persistentDataPath, DataName.FILE_NAME);

ByteBufferbuffer = new ByteBuffer(File.ReadAllBytes(path));

Data.Rootroot = Data.Root.GetRootAsRoot(buffer);

Data.DatadataType = root.DataType;

switch(dataType)
{
caseData.Data.MonsterDataV1:
Data.MonsterDataV1 monsterV1= root.GetData<Data.MonsterDataV1>(new Data.MonsterDataV1());

if(monsterV1 == null)
{
Debug.LogError("Failed load monster data version1.");
return;
}

textVersion.text= "Version1";

if(Encoding.Default != Encoding.UTF8)
textName.text =Encoding.Default.GetString(Encoding.Convert(Encoding.UTF8, Encoding.Default,Encoding.UTF8.GetBytes(monsterV1.Name)));
else
textName.text =monsterV1.Name;
textHp.text =monsterV1.Hp.ToString();
textHitRate.text =monsterV1.HitRate.ToString();
textSpeed.text =monsterV1.Speed.ToString();
textLuck.text =monsterV1.Luck.ToString();
textDefence.text = "Nodata";
break;
}

每种类型的反序列化过程中的过滤器在union中定义的类型。

这意味着的消息传递时你重置数据定义,应该用来读取数据。

测试代码在 github 上的有一些不同。

github 上的代码有 执行加密过程。

(加密 / 解密处理,处理时间会消耗更长的时间......)

FlatBuffers 的思想

cocos2dx spine

json 输出数据是spine的加载很慢。

以二进制格式是相当棒的。

尝试使用及时研究的利与弊??

Unity中使用 FlatBuffers的案例

翻译自: http://exiin.com/blog/flatbuffers-for-unity-sample-code/

1-第一步下载编译器"flatc"和"FlatBuffers.dll"

Flatc.exe 用于将Schema转换成 c#

您可以下载最新的版本 FlatBuffers.dll︰

https://github.com/google/flatbuffers/releases

首先你要下载最新的 flatc.exe 文件,

然后你进入从Github上下载的源代码:路径下的 "/net/FlatBuffers" 文件夹并打开"FlatBuffers.csproj"

并编译出 "FlatBuffers.dll",你需要放到Unity的项目中(assets/plugins文件夹内放)。

下一步是创建一个单独的文件夹,放着我的编译器和 schema 文件︰

Captur52423e

,这里我做了一个批处理脚本%20(%20compile.bat%20)%20包含这些行︰

flatc%20-n%20SaveSchema.txt--gen-onefile%20@pause

%201

2

flatc%20-n%20SaveSchema.txt--gen-onefile

@pause

%20用于演示,我将使用一个%20SaveSchema.txt%20文件︰

//example%20save%20file

namespaceCompanyNamespaceWhatever;

enumColor%20:%20byte%20{%20Red%20=%201,%20Green,%20Blue%20}

unionWeaponClassesOrWhatever%20{%20Sword,%20Gun%20}

structVec3%20{

%20x:float;

%20y:float;

%20z:float;

}

tableGameDataWhatever%20{

%20pos:Vec3;

%20mana:short%20=%20150;

%20hp:short%20=%20100;

%20name:string;

%20inventory:[ubyte];

%20color:Color%20=%20Blue;

%20weapon:WeaponClassesOrWhatever;

}

tableSword%20{

%20damage:int%20=%2010;

%20distance:short%20=%205;

}

tableGun%20{

%20damage:int%20=%20500;

%20reloadspeed:short%20=%202;

}

root_typeGameDataWhatever;

file_identifier"WHAT";

schema%20文件定义了要保存的数据的结构体。

关于schema%20语法的更多信息请阅读这个官方文档页面.

%20一旦你执行compile.bat,它会创建一个名为"SavedSchema.cs"的新文件

2-下一步,我们如何保存我们的数据?

生成的.cs文件包含的所有类和需保存和加载的数据。

到您的项目生成文件的地方

(也请不要忘记将"FlatBuffers.dll"放到您的项目,否则你会看到一些错误)

然后将此代码︰

//%20Create%20flatbufferclass

FlatBufferBuilderfbb%20=%20new%20FlatBufferBuilder(1);

//%20Create%20our%20swordfor%20GameDataWhatever

//------------------------------------------------------

%20

WeaponClassesOrWhateverweaponType%20=%20WeaponClassesOrWhatever.Sword;

Sword.StartSword(fbb);

Sword.AddDamage(fbb,123);

Sword.AddDistance(fbb,999);

Offset<Sword>offsetWeapon%20=%20Sword.EndSword(fbb);

%20

/*

//%20For%20gun%20uncommentthis%20one%20and%20remove%20the%20sword%20one

WeaponClassesOrWhateverweaponType%20=%20WeaponClassesOrWhatever.Gun;

Gun.StartGun(fbb);

Gun.AddDamage(fbb,123);

Gun.AddReloadspeed(fbb,999);

Offset<Gun>offsetWeapon%20=%20Gun.EndGun(fbb);

*/

//------------------------------------------------------

//%20Create%20stringsfor%20GameDataWhatever

//------------------------------------------------------

StringOffset%20cname%20=fbb.CreateString("Test%20String%20!%20time%20:%20"%20+%20DateTime.Now);

//------------------------------------------------------

//%20CreateGameDataWhatever%20object%20we%20will%20store%20string%20and%20weapon%20in

//------------------------------------------------------

GameDataWhatever.StartGameDataWhatever(fbb);

GameDataWhatever.AddName(fbb,cname);

GameDataWhatever.AddPos(fbb,Vec3.CreateVec3(fbb,%201,%202,%201));%20//%20structs%20can%20be%20inserted%20directly,%20no%20need%20tobe%20defined%20earlier

GameDataWhatever.AddColor(fbb,CompanyNamespaceWhatever.Color.Red);

//Store%20weapon

GameDataWhatever.AddWeaponType(fbb,weaponType);

GameDataWhatever.AddWeapon(fbb,offsetWeapon.Value);

var%20offset%20=GameDataWhatever.EndGameDataWhatever(fbb);

//------------------------------------------------------

GameDataWhatever.FinishGameDataWhateverBuffer(fbb,offset);

//%20Save%20the%20datainto%20"SAVE_FILENAME.whatever"%20file,%20name%20doesn't%20matter%20obviously

using%20(var%20ms%20=%20newMemoryStream(fbb.DataBuffer.Data,%20fbb.DataBuffer.Position,%20fbb.Offset))%20{

File.WriteAllBytes("SAVE_FILENAME.whatever",%20ms.ToArray());

%20Debug.Log("SAVED%20!");

}

你写你的数据的方式是%20顺序依赖.

你总是要由内而外创建项目.

从一切开始该对象包含(如字符串、%20数组、%20其他对象)%20的对象本身。

基本上,如果使用了其他的对象,就要预先设置其他的对象!

所以这里发生的是︰

%20我们要先创建的weapon%20和string%20,因为GameDataWhatever里面使用了他们。

储蓄的一部分可以真的很棘手,我劝你读这篇文章有更好的理解如何存储数据。

3-最后,读取文件是小菜一碟。

可以按任何顺序,如果你想要你甚至不需要去通过的所有值,因为flatbuffers%20工作像变魔术一样%20!

ByteBuffer%20bb%20=%20newByteBuffer(File.ReadAllBytes("SAVE_FILENAME.whatever"));

if(!GameDataWhatever.GameDataWhateverBufferHasIdentifier(bb))%20{

%20throw%20new%20Exception("Identifier%20testfailed,%20you%20sure%20the%20identifier%20is%20identical%20to%20the%20generated%20schema'sone?");

}

GameDataWhateverdata%20=%20GameDataWhatever.GetRootAsGameDataWhatever(bb);

Debug.Log("LOADEDDATA%20:%20");

Debug.Log("NAME:%20"%20+%20data.Name);

Debug.Log("POS:%20"%20+%20data.Pos.X%20+%20",%20"%20+%20data.Pos.Y%20+%20",%20"%20+data.Pos.Z);

Debug.Log("COLOR:%20"%20+%20data.Color);

Debug.Log("WEAPONTYPE%20:%20"%20+%20data.WeaponType);

switch(data.WeaponType)%20{

%20case%20WeaponClassesOrWhatever.Sword:

%20Sword%20sword%20=%20new%20Sword();

%20data.GetWeapon<Sword>(sword);

%20Debug.Log("SWORD%20DAMAGE%20:%20"%20+%20sword.Damage);

%20break;

%20case%20WeaponClassesOrWhatever.Gun:

%20Gun%20gun%20=%20new%20Gun();

%20data.GetWeapon<Gun>(gun);

%20Debug.Log("GUN%20RELOAD%20SPEED%20:%20"%20+%20gun.Reloadspeed);

%20break;

%20default:

%20break;

}

我们测试了flatbuffers%20对所有主要的移动平台%20(iOS,Android,亚马逊的操作系统,Windows%20Phone),%20它工作得很好。

4%20Unity中的源代码

如果你想示例代码和已编译的flatbuffer%20文件%20flatc.exe%20和%20flatbuffers.dll,然后下载此文件︰

FlatBuffersTest.zip

来自%20<http://exiin.com/blog/flatbuffers-for-unity-sample-code/>%20

快速上手

接下来将会简要介绍如何使用FlatBuffers。

%20首先,为你要进行序列化操作的数据结构编写一个schema文件。在定义数据结构的属性时,你可以使用基本类型(各种长度的整形与浮点型),也可以是一个字符串,一个任意类型的数组,一个自定义的对象,甚至是一个自定义对象的集合(unions)。属性的值都是允许为空的,同时也可以设置默认值,所以这样一来,每个构建的对象可以根据自己的需要设置属性值。%20然后,用flatc命令(FlatBuffer的编译器)去生成一个C++头文件(或者生成Java/C#/Go/Python等等其他语言的相关文件),进而通过相应的辅助类文件来构建序列化数据。这个生成的C++头文件(比如%20mydata_generated.h)只依赖flatbuffers.h,顺便补充一句,flatbuffers.h里包含了很多核心的函数。%20接着,使用FlatBufferBuilder类去构建一个单层级的二进制流数据。通过之前flatc命令生成好的代码以及FlatBufferBuilder的使用,简单的一些函数调用,就可以让你很自如地向二进制流中加入对象。%20好了,是时候将生成的二进制数据存起来了!或者,你是在做网络通信,那就它这些数据传输出去吧!%20当然,从另一个角度来说,当你获取到二进制数据,要将他解析成对象的时候,你可以将数据指针指向它的根对象,然后你可以在需要的位置方便地将它进行转换,获取你要的属性(object->field())。

开发资源

%20如何构建编译环境,如何在多平台上运行示例代码.%20如何使用编译器.%20如何编写一个schema%20如何将生成的C++代码加入到你自己程序中%20如何将生成的C#/Java代码加入到你自己程序中%20如何将生成的Co代码加入到你自己程序中%20FlatBuffers对平台、语言、特性的支持度%20Benchmarks数据——看看FlatBuffers的优势%20FlatBuffers的白皮书%20FlatBuffers内部结构介绍%20schema正式语法

网络资源

%20GitHub%20库%20官方主页%20FlatBuffers%20Google%20小组%20FlatBuffers%20相关讨论%20独立化实现的工具:FlatCC另一个FlatBuffers的解析工具与代码生成器,运行在C上%20视频资源:Colt’sDevByte.GDC%202015Lightning%20Talk.FlatBuffers%20forGo.Evolution%20of%20FlatBuffersvisualization.其他同仁创作的好文档:FlatBuffers%20in%20GoFlatBuffers%20inAndroidParsing%20JSON%20toFlatBuffers%20in%20JavaFlatBuffers%20inUnity%20

测试案例

http://blog.csdn.net/u010019717

在Google的Benchmark中,已经明确表明了其性能优势,杠杠的啊!

考虑到其扩语言与轻量级的特性,笔者专门自己做了一些较为贴近生产场景的测试,对比的是开发常用的1.1.41版本的fastjson

贴上代码

FlatBuffer与fastjson的比较%20

packageflatbuffers.test;

importjava.nio.ByteBuffer;

importjava.util.Arrays;

importcom.alibaba.fastjson.JSONObject;

importflatbuffers.FlatBufferBuilder;

importflatbuffers.schema.Product;

public%20classBufferCompareTest%20{

%20static%20final%20long%20LOOP_COUNT%20=%205000000;

%20public%20static%20void%20main(String[]%20args)%20{

System.out.println("执行"+LOOP_COUNT+"次解析耗时比较");

%20//FlatBuffers

%20byte[]%20dataByte%20=buildFlatBuffersByte();

%20long%20startFlatBuffers%20=System.currentTimeMillis();

%20Product%20p%20=%20null;

%20for%20(int%20i%20=%200;%20i%20<%20LOOP_COUNT;%20i++){

%20p%20=Product.getRootAsProduct(ByteBuffer.wrap(dataByte));

%20}

%20System.out.println("FlatBuffers%20:"%20+%20(System.currentTimeMillis()%20-%20startFlatBuffers)+"ms");

%20//JSON

%20String%20jsonStr%20="{/"marketable/":/"true/",/"costPrice/":15000,/"imgUrl/":/"http://img2.bbgstatic.com/14e2a07cbd5_2_8f02bdb34427ec107124fd1576287310_800x800.jpeg/",/"productBn/":/"MG120394938912345/",/"productId/":123456,/"productName/":/"德国爱他美Aptamil Pre段 0-3个月 日期至16年7-8月左右/",/"salePrice/":15500,/"shopId/":123,/"shopName/":/"云猴全球购/"}";

long startJSON =System.currentTimeMillis();

JSONObject obj = null;

for (int i = 0; i < LOOP_COUNT; i++){

obj =JSONObject.parseObject(jsonStr);

}

System.out.println("JSON : "+ (System.currentTimeMillis() - startJSON)+"ms");

}

private static byte[]buildFlatBuffersByte() {

FlatBufferBuilder fbb = newFlatBufferBuilder(1);

int productBnOS =fbb.createString("MG120394938912345");

int productNameOS =fbb.createString("德国爱他美Aptamil Pre段 0-3个月 日期至16年7-8月左右");

int shopNameOS =fbb.createString("云猴全球购");

int imgUrlOS =fbb.createString("http://img2.bbgstatic.com/14e2a07cbd5_2_8f02bdb34427ec107124fd1576287310_800x800.jpeg");

//注意,创建字符串(createString)要在XXX.startXXX方法执行之前,否则会出现异常

Product.startProduct(fbb);

Product.addProductId(fbb, 123456l);

Product.addShopId(fbb, 123);

Product.addProductBn(fbb, productBnOS);

Product.addProductName(fbb,productNameOS);

Product.addShopName(fbb, shopNameOS);

Product.addImgUrl(fbb, imgUrlOS);

Product.addCostPrice(fbb, 15000l);

Product.addSalePrice(fbb, 15500l);

Product.addMarketable(fbb, true);

int endOffset =Product.endProduct(fbb);

Product.finishProductBuffer(fbb,endOffset);

byte[] originalData =fbb.dataBuffer().array();

byte[] dataByte =Arrays.copyOfRange(originalData, fbb.dataBuffer().position(),(fbb.dataBuffer().position() +fbb.offset()));

return dataByte;

}

}

结果是,还是有点差距的:

执行5000000次解析耗时比较

FlatBuffers : 98ms

JSON : 10375ms

http://blog.csdn.net/u010019717


第七城市

最新文章

123

最新摄影

微信扫一扫

第七城市微信公众平台