替代Protocol buffers 的FlatBuffers:高效利用内存的序列化函数库(Unity中测试)
孙广东 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 文件︰
,这里我做了一个批处理脚本 ( compile.bat ) 包含这些行︰
flatc -n SaveSchema.txt--gen-onefile @pause
1 2 |
flatc -n SaveSchema.txt --gen-onefile @pause |
用于演示,我将使用一个 SaveSchema.txt 文件︰
//example save file
namespaceCompanyNamespaceWhatever;
enumColor : byte { Red = 1, Green, Blue }
unionWeaponClassesOrWhatever { Sword, Gun }
structVec3 {
x:float;
y:float;
z:float;
}
tableGameDataWhatever {
pos:Vec3;
mana:short = 150;
hp:short = 100;
name:string;
inventory:[ubyte];
color:Color = Blue;
weapon:WeaponClassesOrWhatever;
}
tableSword {
damage:int = 10;
distance:short = 5;
}
tableGun {
damage:int = 500;
reloadspeed:short = 2;
}
root_typeGameDataWhatever;
file_identifier"WHAT";
schema 文件定义了要保存的数据的结构体。
关于schema 语法的更多信息请阅读这个官方文档页面.
一旦你执行compile.bat,它会创建一个名为"SavedSchema.cs"的新文件
Flatc现在生成的几个类的名称,如"WeaponClassesOrWhatever"
这个C#文件是你整个系统的加载和保存schema的作用。
2-下一步,我们如何保存我们的数据?
生成的.cs文件包含的所有类和需保存和加载的数据。
到您的项目生成文件的地方
(也请不要忘记将"FlatBuffers.dll"放到您的项目,否则你会看到一些错误)
然后将此代码︰
// Create flatbufferclass
FlatBufferBuilderfbb = new FlatBufferBuilder(1);
// Create our swordfor GameDataWhatever
//------------------------------------------------------
WeaponClassesOrWhateverweaponType = WeaponClassesOrWhatever.Sword;
Sword.StartSword(fbb);
Sword.AddDamage(fbb,123);
Sword.AddDistance(fbb,999);
Offset<Sword>offsetWeapon = Sword.EndSword(fbb);
/*
// For gun uncommentthis one and remove the sword one
WeaponClassesOrWhateverweaponType = WeaponClassesOrWhatever.Gun;
Gun.StartGun(fbb);
Gun.AddDamage(fbb,123);
Gun.AddReloadspeed(fbb,999);
Offset<Gun>offsetWeapon = Gun.EndGun(fbb);
*/
//------------------------------------------------------
// Create stringsfor GameDataWhatever
//------------------------------------------------------
StringOffset cname =fbb.CreateString("Test String ! time : " + DateTime.Now);
//------------------------------------------------------
// CreateGameDataWhatever object we will store string and weapon in
//------------------------------------------------------
GameDataWhatever.StartGameDataWhatever(fbb);
GameDataWhatever.AddName(fbb,cname);
GameDataWhatever.AddPos(fbb,Vec3.CreateVec3(fbb, 1, 2, 1)); // structs can be inserted directly, no need tobe defined earlier
GameDataWhatever.AddColor(fbb,CompanyNamespaceWhatever.Color.Red);
//Store weapon
GameDataWhatever.AddWeaponType(fbb,weaponType);
GameDataWhatever.AddWeapon(fbb,offsetWeapon.Value);
var offset =GameDataWhatever.EndGameDataWhatever(fbb);
//------------------------------------------------------
GameDataWhatever.FinishGameDataWhateverBuffer(fbb,offset);
// Save the datainto "SAVE_FILENAME.whatever" file, name doesn't matter obviously
using (var ms = newMemoryStream(fbb.DataBuffer.Data, fbb.DataBuffer.Position, fbb.Offset)) {
File.WriteAllBytes("SAVE_FILENAME.whatever", ms.ToArray());
Debug.Log("SAVED !");
}
你写你的数据的方式是 顺序依赖.
你总是要由内而外创建项目.
从一切开始该对象包含(如字符串、 数组、 其他对象) 的对象本身。
基本上,如果使用了其他的对象,就要预先设置其他的对象!
所以这里发生的是︰
我们要先创建的weapon 和string ,因为GameDataWhatever里面使用了他们。
储蓄的一部分可以真的很棘手,我劝你读这篇文章有更好的理解如何存储数据。
3-最后,读取文件是小菜一碟。
可以按任何顺序,如果你想要你甚至不需要去通过的所有值,因为flatbuffers 工作像变魔术一样 !
ByteBuffer bb = newByteBuffer(File.ReadAllBytes("SAVE_FILENAME.whatever"));
if(!GameDataWhatever.GameDataWhateverBufferHasIdentifier(bb)) {
throw new Exception("Identifier testfailed, you sure the identifier is identical to the generated schema'sone?");
}
GameDataWhateverdata = GameDataWhatever.GetRootAsGameDataWhatever(bb);
Debug.Log("LOADEDDATA : ");
Debug.Log("NAME: " + data.Name);
Debug.Log("POS: " + data.Pos.X + ", " + data.Pos.Y + ", " +data.Pos.Z);
Debug.Log("COLOR: " + data.Color);
Debug.Log("WEAPONTYPE : " + data.WeaponType);
switch(data.WeaponType) {
case WeaponClassesOrWhatever.Sword:
Sword sword = new Sword();
data.GetWeapon<Sword>(sword);
Debug.Log("SWORD DAMAGE : " + sword.Damage);
break;
case WeaponClassesOrWhatever.Gun:
Gun gun = new Gun();
data.GetWeapon<Gun>(gun);
Debug.Log("GUN RELOAD SPEED : " + gun.Reloadspeed);
break;
default:
break;
}
我们测试了flatbuffers 对所有主要的移动平台 (iOS,Android,亚马逊的操作系统,Windows Phone), 它工作得很好。
4 Unity中的源代码
如果你想示例代码和已编译的flatbuffer 文件 flatc.exe 和 flatbuffers.dll,然后下载此文件︰
FlatBuffersTest.zip
来自 <http://exiin.com/blog/flatbuffers-for-unity-sample-code/>
快速上手
接下来将会简要介绍如何使用FlatBuffers。
- 首先,为你要进行序列化操作的数据结构编写一个schema文件。在定义数据结构的属性时,你可以使用基本类型(各种长度的整形与浮点型),也可以是一个字符串,一个任意类型的数组,一个自定义的对象,甚至是一个自定义对象的集合(unions)。属性的值都是允许为空的,同时也可以设置默认值,所以这样一来,每个构建的对象可以根据自己的需要设置属性值。
- 然后,用flatc命令(FlatBuffer的编译器)去生成一个C++头文件(或者生成Java/C#/Go/Python等等其他语言的相关文件),进而通过相应的辅助类文件来构建序列化数据。这个生成的C++头文件(比如 mydata_generated.h)只依赖flatbuffers.h,顺便补充一句,flatbuffers.h里包含了很多核心的函数。
- 接着,使用FlatBufferBuilder类去构建一个单层级的二进制流数据。通过之前flatc命令生成好的代码以及FlatBufferBuilder的使用,简单的一些函数调用,就可以让你很自如地向二进制流中加入对象。
- 好了,是时候将生成的二进制数据存起来了!或者,你是在做网络通信,那就它这些数据传输出去吧!
- 当然,从另一个角度来说,当你获取到二进制数据,要将他解析成对象的时候,你可以将数据指针指向它的根对象,然后你可以在需要的位置方便地将它进行转换,获取你要的属性(object->field())。
开发资源
- 如何构建编译环境,如何在多平台上运行示例代码.
- 如何使用编译器.
- 如何编写一个schema
- 如何将生成的C++代码加入到你自己程序中
- 如何将生成的C#/Java代码加入到你自己程序中
- 如何将生成的Co代码加入到你自己程序中
- FlatBuffers对平台、语言、特性的支持度
- Benchmarks数据——看看FlatBuffers的优势
- FlatBuffers的白皮书
- FlatBuffers内部结构介绍
- schema正式语法
网络资源
- GitHub 库
- 官方主页
- FlatBuffers Google 小组
- FlatBuffers 相关讨论
- 独立化实现的工具:FlatCC另一个FlatBuffers的解析工具与代码生成器,运行在C上
- 视频资源:
- Colt’sDevByte.
- GDC 2015Lightning Talk.
- FlatBuffers forGo.
- Evolution of FlatBuffersvisualization.
- 其他同仁创作的好文档:
- FlatBuffers in Go
- FlatBuffers in Android
- Parsing JSON to FlatBuffers in Java
- FlatBuffers in Unity
测试案例
http://blog.csdn.net/u010019717
在Google的Benchmark中,已经明确表明了其性能优势,杠杠的啊!
考虑到其扩语言与轻量级的特性,笔者专门自己做了一些较为贴近生产场景的测试,对比的是开发常用的1.1.41版本的fastjson
贴上代码
FlatBuffer与fastjson的比较
packageflatbuffers.test;
importjava.nio.ByteBuffer;
importjava.util.Arrays;
importcom.alibaba.fastjson.JSONObject;
importflatbuffers.FlatBufferBuilder;
importflatbuffers.schema.Product;
public classBufferCompareTest {
static final long LOOP_COUNT = 5000000;
public static void main(String[] args) {
System.out.println("执行"+LOOP_COUNT+"次解析耗时比较");
//FlatBuffers
byte[] dataByte =buildFlatBuffersByte();
long startFlatBuffers =System.currentTimeMillis();
Product p = null;
for (int i = 0; i < LOOP_COUNT; i++){
p =Product.getRootAsProduct(ByteBuffer.wrap(dataByte));
}
System.out.println("FlatBuffers :" + (System.currentTimeMillis() - startFlatBuffers)+"ms");
//JSON
String jsonStr ="{\"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
替代Protocol buffers 的FlatBuffers:高效利用内存的序列化函数库(Unity中测试)相关推荐
- 一个完整利用InternetOpen等系列函数进行下载的测试例子
2011-08-14 12:03 一个完整利用InternetOpen等系列函数进行下载的测试例子 // TODO: Add extra validation here HINTERNET hsse ...
- 【STM32】利用 C 语言 strchar() 函数查找字符串中指定字符的位置
文章目录 字符串中查找字符 strchr() 描述 声明 参数 返回值 字符串分割 strtok() 描述 声明 参数 返回值 自己的函数 字符串中查找字符 strchr() 描述 C 库函数 cha ...
- 利用python的强大函数库,实现波形的小波降噪、带通滤波、时阈分析、FFT波形转换
import math import matplotlib.pyplot as plt import pywt import pandas as pd import numpy as np impor ...
- Protocol Buffers的应用
1. Protocol Buffers的介绍 Protocol buffers are Google's language-neutral, platform-neutral, extensible ...
- ceaspectusS成熟船公司航运人工智能随时随地验箱上报箱况+箱轨迹报备,实现集装箱套箱云堆场,精细化箱管控+好箱高效利用
上海人工智能领军企业ceaspectusS™成熟船公司航运人工智能,随时随地验箱上报箱况+箱轨迹报备,实现套箱/云堆场,提升船公司精细化智能化箱管控+好箱利用率. 通过CIMCAI全球领先的成熟航运人 ...
- ios开发中计算代码运算时间_理解Unity中的优化(二):内存
内存: 内存消耗是一个关键的性能指标,尤其是在内存资源有限的平台上,比如低端移动设备. 内存消耗分析: 在Unity中诊断内存问题,Unity介绍了一款开元的可视化内存分析工具--MemoryProf ...
- FlatBuffers vs Protocol Buffers
FlatBuffers去年发布,最近看了一下,与同是出自Google之手的Protocol Buffers非常类似.在官网上介绍,FlatBuffers(简称FB)主要针对game developme ...
- JSON与Protocol Buffers的一些比较
转载来源:http://www.cnblogs.com/zhubo/archive/2011/07/06/JSON_And_ProtocolBuffers.html JSON与Protocol Buf ...
- C++程序员Protocol Buffers基础指南
这篇教程提供了一个面向 C++ 程序员关于 protocol buffers 的基础介绍.通过创建一个简单的示例应用程序,它将向我们展示: 在 .proto 文件中定义消息格式 使用 protocol ...
最新文章
- dropbear编译安装及服务脚本编写
- 由粗到精学习LVI-SAM:论文原文解析
- 5分钟教你写出一份完美的PRD文档(附案例)
- 基于android的条码识别技术,基于Android手机的条码识别系统研究
- 我的Python成长之路---第六天---Python基础(19)---2016年2月20日(晴)
- java中dynamic_java中dynamic web project与web project 的区别 [转]
- Azure Linux VM密钥登录
- Axure 9 案例教程基础篇之课程简介(助你快速进入实战阶段)
- 锐起3.1无盘服务器,[迎新春]锐起3.1无盘XP万能包13V2(IE8版本)
- 51单片机SG90舵机控制原理
- 经纬度十进制与度分秒换算及数据库实现
- SDCC 2017·深圳站八大不容错过的理由
- Angular导出功能(excel导出功能、文件数据流导出功能、图片的下载导出功能)
- 所生成项目的处理器架构“MSIL”与引用“***”的处理器架构“x86”不匹配。这种不匹配可能会导致运行时失败。请考虑通过配置管理器更改您的项目的目标处理器架构,以使您的项目与引用间的处理器架构...
- Bootstrap 徽章
- antv/G6自定义边
- 动态图表交互揭秘:制作选择器的奥秘
- 透彻理解css的grid布局
- 付款条件Payment Terms
- 软件测试失业了如何再就业
热门文章
- java计算机毕业设计精准扶贫管理系统源程序+mysql+系统+lw文档+远程调试
- upupw 5.4配置的php,《Zendstudio12+UPUPW+PHP5.4开发平台配置过程》,upupwzendguard_PHP教程...
- linux centos 7查看网卡信息,CentOS 7系统中查看网卡信息
- React boil
- python程序设计请计算从公元1年1月_使用Python计算今天距离公元1年1月1日的天数...
- 广告点阵屏和熟悉字模软件的使用
- 开店创业缺什么都不能缺水獭掌柜,外卖接单有它太省心
- 项目实训记录(1-2周)
- 微信小程序直播消耗服务器流量,微信小程序直播强大流量的背后
- 还在为投稿发愁吗?ICCVIT 2023,一个计算机、视觉与智能技术国际会议