@fenjuly
2016-08-21T06:00:26.000000Z
字数 5385
阅读 3289
FlatBuffers是一个高效的跨平台序列化类库,可以在C++、C#、C、Go、Java、JavaScript、PHP和Python中使用。是Google开发的,是为了应用在游戏开发,以及其他注重性能的应用上。
它主要有以下几个特点:
不需要打包/解包。它的结构化数据都以二进制形式保存,不需要数据解析过程,数据也可以方便传递。
省内存、性能好。访问数据时只需要访问内存中的缓冲区。它不需要多余的内存分配(至少在C++是这样,其他语言中可能会有变动)。FlatBuffers还适合配合mmap或数据流使用,只需要缓冲区的一部分存储在内存中。访问时速度接近原结构访问,只有一点延迟(一种虚函数表vtable),是为了允许格式升级以及可选字段。FlatBuffers适合那些花费了大量时间和空间(内存分配)来访问和构建序列化数据的项目,比如游戏以及其他对表现敏感的应用。
强类型系统,在编译阶段就能预防一些bug的产生。
跨平台。
FlatBuffers的功能和Protocol Buffers很像,他们的最大不同点是在使用具体的数据之前,FlatBuffers不需要解析/解包的过程。
JSON作为数据交换格式,被广泛用户各种动态语言之间(当然也包括静态语言)。它的优点是易于理解(可读性好),同时它的最大的缺点那就是解析时的性能问题了。而且因为它的动态类型特点,你的代码可能还需要多写好多类型、数据检查逻辑。
详细用法可以参考谷歌提供的文档
// Copyright 2015 Google Inc. All rights reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
namespace sample;
table Animal {
name:string;
sound:string;
}
root_type Animal;
root_type UserList;
-c -o ./ ./Animal.fbs
String name = builder.createString("brid");
String sound = builder.createString("sweat");
Animal.startAnimal(builder);
Animal.addName(builder, name);
Animal.addSound(builder, sound);
int brid = Animal.endAnimal(builder);
builder.finish(brid);
// We now have a FlatBuffer that can be stored on disk or sent over a network.
<div class="md-section-divider"></div>
// This must be called after `finish()`.
java.nio.ByteBuffer buf = builder.dataBuffer();
// The data in this ByteBuffer does NOT start at 0, but at buf.position().
// The number of bytes is buf.remaining().
// Alternatively this copies the above data out of the ByteBuffer for you:
bytes[] buf = builder.sizedByteArray();
<div class="md-section-divider"></div>
// We can access the buffer we just made directly. Pretend this came over a
// network, was read off of disk, etc.
java.nio.ByteBuffer buf = builder.dataBuffer();
// Deserialize the data from the buffer.
Animal animal = Animal.getRootAsAnimal(buf);
<div class="md-section-divider"></div>
利用FlatBuffers来进行数据保存及传输的优点显而易见,它利用自身特殊的编码格式,能一定程度上减少内存的占用,优化读取的性能。它对于数据结构的向前向后兼容提供了很好的扩展性,方便又高效:
要想让数据结构具有可扩展性,需将数据结构定义为table,它是数据扩展的基础,FlatBuffers中的struct类型不支持扩展
如果想在后续的版本中删除数据结构中的某些字段,只要在将要删除的字段后面添加(deprecated)即可,当然需要保证删除的字段在之前版本的程序中不会引起程序崩溃(该删掉的字段在上一版本的程序中获取到的会是个空指针或空值,只需保证程序在获取到空值或空指针之后不会出现异常即可)
如果想在后续版本中向数据结构中添加某些字段,需添加到table中最后一个字段的后面。
因为我们个性化业务需要读取数据基本上都是Json,所以这里拿FlatBuffers与Json做个对比。这里以一个457KB大小的Json数据作为数据源,它经过转换成为FlatBuffers二进制文件后大小为354KB。主要做三个测试:
从两个方面来进行测试:1.读取速度。2.读取过程中的内存占用。
测试方式:用三种方式来读取Json数据文件,每种方式循环1000次,取平均值。
测试机型:三星Note3。
内容 | 原生Json | Json To FB | 原生FlatBuffers |
---|---|---|---|
耗时 | 181ms | 148ms | 8.849ms |
原生Json内存占用,依次是原生Json,Json To FB,原生FlatBuffers。
可以看出,无论是读取速度还是读取过程中的内存占用,原生Json读取性能最差,将Json转成FlatBuffers二进制文件后读取表现要稍微好一点,原生FlatBuffers读取表现很优越。
这里参照了网上别人的对比文章。
http://blog.csdn.net/menggucaoyuan/article/details/34409433
测试用例为:序列化一个通讯录personal_info_list(table),通讯录可以认为是有每个人的信息(personal_info)的集合。每个人信息personal_info(table)有:个人id(uint)、名字(string)、年龄(byte)、性别(enum, byte)和电话号码(ulong)。
测试时,在内存中构造37个personal_info对象,并序列化之,重复这个过程100万次,然后再进行反序列化,再重复100万次。
测试环境:12Core Intel(R) Xeon(R) CPU E5-2620 0 @ 2.00GHz
bin/tellist_pb
encode: loop = 1000000, time diff = 14210ms
decode: loop = 1000000, time diff = 11185ms
buf size:841
bin/tellist_pb
encode: loop = 1000000, time diff = 14100ms
decode: loop = 1000000, time diff = 11234ms
buf size:841
bin/tellist_pb
encode: loop = 1000000, time diff = 14145ms
decode: loop = 1000000, time diff = 11237ms
buf size:841
序列化后占用内存空间841Byte,encode平均运算时间42455ms / 3 = 14151.7ms,decode平均计算时间33656ms / 3 = 11218.7ms
<div class="md-section-divider"></div>
bin/tellist_fb
encode: loop = 1000000, time diff = 11666ms
decode: loop = 1000000, time diff = 1141ms
buf size:1712
bin/tellist_fb
encode: loop = 1000000, time diff = 11539ms
decode: loop = 1000000, time diff = 1200ms
buf size:1712
bin/tellist_fb
encode: loop = 1000000, time diff = 11737ms
decode: loop = 1000000, time diff = 1141ms
buf size:1712
序列化后占用内存空间1712Byte,encode平均运算时间34942ms / 3 = 11647.3ms,decode平均计算时间3482ms / 3 = 1160.7ms
可以看出:
1712 byte
>841 byte
)11647.3 ms
< 14151.7 ms
)1160.7 ms
<11218 ms
)http://www.carrotsight.com/2015/10/10/FlatBuffers%E5%AD%A6%E4%B9%A0%E6%80%BB%E7%BB%93.html
官方也给出了测试结果http://google.github.io/flatbuffers/flatbuffers_benchmarks.html
方案一:在使用FlatBuffers时,我们需要先编写scheme文件,然后通过flatc编译器编译出一些Java Bean文件。那么我们可以服务器直接下发一个FlatBuffers二进制文件,然后用这些Bean文件替换之前的Bean文件,这样做虽然客户端这边能大把减少解析时间,但工作量太大了。
方案二:退而求其次,服务器还是直接下发Json文件,客户端这边将Json To Bean这一层替换为 Json To FlatBuffers To Bean。经过研究发现FlatBuffers生成的Bean文件里面的每一个属性都有一个offset指,而FlatBuffers解析二进制文件的时候就是根据这个offset值来判断目前读取的值是属于哪个变量的。现在的解决办法就是服务器端在生成FlatBuffers Bean文件之后,我们得知这些FlatBuffers Bean属性的offset值,然后我们在客户端里面编码来替换Json To Bean这一层。这样的做法从前面的实验能看出来也能减少一部分解析时间。