FlatBuffers发布出来一周多,周末便抽时间先研究下它的使用方法。Flatbuffers的idl的语法主要参考[http://google.github.io/flatbuffers/md__schemas.html ]。本文主要介绍几个它的monster.fbs没有给出说明的几个语法点和相关的注意事项。

1 comment

  它的注释中介绍了”///",说明是可以生成document comment. 我写了如下fbs代码(假设文件名称还是monster.fbs):
/// struct Vec3 {
/// x:float;
/// y:float;
/// z:float;
/// }

  "flatc -c monster.fbs"命令编译后,生成代码为:
/// struct Vec3 { x:float; y:float; z:float; }

2 struct
  struct默认的aliagnment是4Byte.

  如下fbs代码(假设文件名称还是monster.fbs):
struct Vec4 {
x : float;
y : short;
z : float;
w : short;
}

  "flatc -c monster.fbs"命令编译后,生成代码为:
MANUALLY_ALIGNED_STRUCT(4) Vec4 {
private:
float x_;
int16_t y_;
int16_t __padding0;
float z_;
int16_t w_;
int16_t __padding1;

public:

Vec4(float x, int16_t y, float z, int16_t w)
: x_(flatbuffers::EndianScalar(x)), y_(flatbuffers::EndianScalar(y)), __padding0(0), z_(flatbuffers::EndianScalar(z)), w_(flatbuffers::EndianScalar(w)), __padding1(0) {}
float x() const { return flatbuffers::EndianScalar(x_); }
int16_t y() const { return flatbuffers::EndianScalar(y_); }
float z() const { return flatbuffers::EndianScalar(z_); }
int16_t w() const { return flatbuffers::EndianScalar(w_); }
};
STRUCT_END(Vec4, 16);

3 original_order
  FB(FlatBuffers的简称,下同)用original_order来保持fbs中table的field顺序,FB说明中说明它是用来修饰table的(”original_order (on a table)"),其实他也可以修饰struct。
  如下fbs代码(假设文件名称还是monster.fbs):
struct Vec4_ (original_order) {
x : float;
y : short;
z : float;
w : short;
}

  "flatc -c monster.fbs"命令编译后,生成代码为:
STRUCT_END(Vec4, 16);
MANUALLY_ALIGNED_STRUCT(4) Vec4_ {
private:
float x_;
int16_t y_;
int16_t __padding0;
float z_;
int16_t w_;
int16_t __padding1;

public:
Vec4_(float x, int16_t y, float z, int16_t w)
: x_(flatbuffers::EndianScalar(x)), y_(flatbuffers::EndianScalar(y)), __padding0(0), z_(flatbuffers::EndianScalar(z)), w_(flatbuffers::EndianScalar(w)), __padding1(0) {}

float x() const { return flatbuffers::EndianScalar(x_); }
int16_t y() const { return flatbuffers::EndianScalar(y_); }
float z() const { return flatbuffers::EndianScalar(z_); }
int16_t w() const { return flatbuffers::EndianScalar(w_); }
};
STRUCT_END(Vec4_, 16);

4 force_align
  force_align这个关键字用来对struct进行align,此处我想说明FB的一个bug。
  如下fbs代码(假设文件名称还是monster.fbs):
struct Vec3 (force_align : 8 ) {
x : float;
y : float;
z : float;
}

  "flatc -c monster.fbs"命令编译后,生成代码为:
MANUALLY_ALIGNED_STRUCT(8) Vec3 {
private:
float x_;
float y_;
float z_;

public:
Vec3(float x, float y, float z)
: x_(flatbuffers::EndianScalar(x)), y_(flatbuffers::EndianScalar(y)), z_(flatbuffers::EndianScalar(z)) {}

float x() const { return flatbuffers::EndianScalar(x_); }
float y() const { return flatbuffers::EndianScalar(y_); }
float z() const { return flatbuffers::EndianScalar(z_); }
};
STRUCT_END(Vec3, 12);

  请注意上面最后一行代码的最后一个参数"12”,我已经说明以8Byte作为alignment,但是它给出的struct Vec3的size仍然为12,如果你使用上面的代码,g++会给出这样的错误提示"error: static assertion failed: compiler breaks packing rules”。
  解决方法有三个,第一,你把这行代码注释掉。or 第二,把数字“12”手工改成“16”。or 第三,等待官方修正这个bug(我已经提交了这个bug:https://github.com/google/flatbuffers/issues/18)。

  补充:bug已经由gwvo(https://github.com/gwvo)修正了。修正链接见(https://github.com/AlexStocks/flatbuffers/commit/65cfa18855abc712faa1bf0cb5c3b88ab8df4b28)。

  之所以Vec3的size没有计算正确,原因是作者分析完struct的各个成员后忘了这个语法特性了。我下面稍微补充下FB的编译器出现这个bug的原因。

  bug修改之前,void Parser::ParseDecl() 一块代码如下:

// 验证struct的开头为 "{"
Expect('{');
// 分析struct的每一行(field),其主功能是为struct添加member并增加struct的bytesize
while (token_ != '}') ParseField(struct_def);
// 为struct添加padding成员以进行alignment,然后计算struct的bytesize值
// 注意:作者忘了"force_align"这个关键语法特性就计算struct的bytesize
struct_def.PadLastField(struct_def.minalign);
// 验证struct的结尾为 "}"
Expect('}');
// 此时才开始查找"force_align"这个keyword
auto force_align = struct_def.attributes.Lookup("force_align");
if (fixed && force_align) {
auto align = static_cast<size_t>(atoi(force_align->constant.c_str()));
// 虽然计算出了struct应该以align为基准进行alignment,但是为时已晚,无法改变struct的bytesize了
struct_def.minalign = align;
}

  通过上面分析,就可以晓得问题所在,修正后的代码为:

Expect('{');
while (token_ != '}') ParseField(struct_def);
auto force_align = struct_def.attributes.Lookup("force_align");
if (fixed && force_align) {
auto align = static_cast<size_t>(atoi(force_align->constant.c_str()));
struct_def.minalign = align;
}
struct_def.PadLastField(struct_def.minalign); // !!!
Expect('}'); // !!!
}

  注意上面的代码块,仅仅是对"!!!"标识的两行代码在代码块中往下移动了几行,就可以正确计算struct的bytesize了。再次感谢gwvo的工作。

5 struct&table

  table 是FB实现向前\向后兼容的关键。table每个field默认都是optional的,而struct的每个成员是required的。把idl转换为C++ or Java语言时候,FB不会修改struct成员的顺序(如果struct的成员无法alignment FB会自动添加padding成员),但是会改变table成员的顺序以使得它占用最小的内存空间。所以,table 提供向前\向后兼容特性,而你一旦定义一个struct后,你就无法再添加新的member。

  table的每个对象被序列化后的内存空间中都存在着一个额外(除却数据内存空间外)的辅助空间以说明其内存对象分布,称之为vtable。通过vtable我们可以知道这个table对象有哪些成员。如果同一个table的不同对象被序列化进同一个内存空间内,那么只有第一个对象存储了vtable的详细数据,而其他对象的vtable区域只存储一个指向第一个对象的vtable的指针即可。因为table对象需要vtable以说明自己,所以对他它序列化后占用的内存空间一般都比序列化一个struct对象后占用的内存空间大。

  vtable的每个元素占用2Bytes。它的第一个element是vtable的elements总数目(包含第一个element自身),第二个是对象的以字节为单位的内存大小(包含vtable占用的内存空间大小),后面的element就是table各个成员在object区域的伪指针(即偏移大小)。如果table有N个member,则vtable大小就是((N + 2) * 2) Bytes.

  FB的对象区域每个成员基本上都是len+value形式。vtable内则是type id + offset形式。offset可以理解为一个伪指针,通过它可以找到每个成员。FB对每个成员进行赋值之前,要先进性alignment,然后把value转换为endian形式,FB保证这些值在不同平台上都有效。

  反序列化一个对象后,访问一个对象的某个成员时,如果其序列化后的空间内没有这个成员的数据,那么就返回其默认值。同理,序列化一个对象时,如果这个对象的成员的值与默认值相等,则不会把这个值序列化进内场空间。如果一个table的field都用默认值,那么此时对它序列化后占用的内存空间要比序列化一个struct对象后占用的内存空间小。

  FB还有一个类型union,它的文档里面提到"FlatBuffers can of course be wrapped inside other containers where needed, or you can use its union feature to dynamically identify multiple possible sub-objects stored. Additionally, it can be used together with the schema parser if full reflective capabilities are desired.“。我理解不多,有待后面分析。

6 summary

  FB的document里面提到开发它的缘由。 
  “在过去的好日子里,说到提高性能就是开发更高效的CPU指令和提供更短的CPU计算周期,但现在那个好日子一去不复返了,因为回首回望,我们发现CPU已经发展太快而把他的小弟内存拉下了好几圈了。现在提高性能的战场应该是在内存而非CPU了。快速高效的方法应该是:一个对象在内存中如何高效的占用尽可能少的空间,如何更快速的访问它,以及怎么为它分配这些空间和如何拷贝它。” 
  “进程一个重要的是任务就是对它的数据进行序列化,这可能要使用很多临时的变量以分析和暂时存储这些数据,也可能使用不甚高效的内存分配策略。而FB可以做到不使用临时对象、没有额外的内存分配、不拷贝和让对象占用尽可能少的内存空间。除了做到这些,FB能保证数据本身的向前\向后兼容性、跨平台特性。另外,它以小端数据格式作为标准数据格式” 
  “FB主要关注移动开发平台(这个平台与PC平台相比就如同上世界把PC与大型机相比一样,它的内存空间大小和数据传输速度都稍显紧缺),它尤其关注一类移动应用:games” 
  根据我对FB源码的研究,诚哉斯言!

FlatBuffers要点的更多相关文章

  1. C++常见笔试面试要点以及常见问题

    1. C++常见笔试面试要点: C++语言相关: (1) 虚函数(多态)的内部实现 (2) 智能指针用过哪些?shared_ptr和unique_ptr用的时候需要注意什么?shared_ptr的实现 ...

  2. 《高性能javascript》一书要点和延伸(上)

    前些天收到了HTML5中国送来的<高性能javascript>一书,便打算将其做为假期消遣,顺便也写篇文章记录下书中一些要点. 个人觉得本书很值得中低级别的前端朋友阅读,会有很多意想不到的 ...

  3. [php]laravel框架容器管理的一些要点

    本文面向php语言的laravel框架的用户,介绍一些laravel框架里面容器管理方面的使用要点.文章很长,但是内容应该很有用,希望有需要的朋友能看到.php经验有限,不到位的地方,欢迎帮忙指正. ...

  4. 基础笔记(一):C#编程要点

    前言 来源于手中日常摘录的资料和书籍,算是对看过的东西的总结,部分注有阅读心得,也有部分只提出大纲或结论.(备注:本篇文章中大部分要点需要有实际的开发经验,有助于阅读理解.)     目录 const ...

  5. CORS基础要点:关于dataType、contentType、withCredentials

    事实上,面试时我喜欢问跨域,因为多数开发者都知道它并且常用,而我希望能从面试者的回答中知道他在这个问题的深入程度,进一步看看面试者研究问题的思维方式及钻研精神,然而确实难到了很多人,当然这也不是面试通 ...

  6. 漫谈C++:良好的编程习惯与编程要点

    以良好的方式编写C++ class 假设现在我们要实现一个复数类complex,在类的实现过程中探索良好的编程习惯. ① Header(头文件)中的防卫式声明 complex.h: # ifndef ...

  7. 推荐Linux管理员不可不知十大PHP安全要点 - SCutePHP

    PHP是使用最广泛的脚本编程语言之一.市场份额颇能说明其主导地位.PHP 7已推出,这个事实让这种编程语言对当前的开发人员来说更具吸引力.尽管出现了一些变化,但是许多开发人员对PHP的未来持怀疑态度. ...

  8. PHPCMS与UCenter整合要点

    要点一: PHPCMS不能直接与UCenter整合,而是要经过 PHPSSO 适配,因此应用主URL应是 http://phpcms_url/phpsso_server 这种模式的. 要点二: 因为 ...

  9. EDM制作要点

    EDM是Email Direct Marketing的缩写,虽然也是html,但是和我们在网页上使用的html不同,因为安全原因,各大邮箱服务商级邮件客户端都会对邮件内容进行一定程度上的处理,不会按照 ...

随机推荐

  1. sleep和wait到底什么区别

    wait是在当前线程持有wait对象锁的情况下,暂时放弃锁,并让出CPU资源,并积极等待其它线程调用同一对象的notify或者notifyAll方法.注意,即使只有一个线程在等待,并且有其它线程调用了 ...

  2. Passing JavaScript Objects to Managed Code

    Silverlight If the target managed property or input parameter is strongly typed (that is, not typed ...

  3. eclipse中tomcat使用add and remove无法发布web项目

    继上次启动eclipse中的tomcat报classNotFound的问题后,这次又遇到新问题.就是右键点击tomcat使用add and remove发布web项目至tomcat后,启动tomcat ...

  4. css div居中显示的4种写法

    Demo:http://www.feman.cn/h5/center.html .absolute 绝对定位 这是我们最常用的一种居中定位写法 要求必须确定div的宽高度 目前市面上的浏览器基本上都支 ...

  5. 总结Selenium自动化测试方法(三)WebDriver定位元素方法

    三.WebDriver定位元素 推荐使用的webdriver是firefox,因为他的firebug更能可以帮助定位页面元素使用 # create a new Firefox session cls. ...

  6. 10.10 dos实验

    一. 实验目的 (1)认识DOS: (2)掌握命令解释程序的原理: (3)掌握简单的DOS调用方法: (4)掌握C语言编程初步. 二. 实验内容和要求 编写类似于DOS,UNIX的命令行解释程序 (1 ...

  7. Android开发之PopupWindow

      /* *  Android开发之PopupWindow * *  Created on: 2011-8-8 *  Author: blueeagle *  Email: liujiaxiang@g ...

  8. iso中第三方框架SDWebImage的使用步骤

    一.SDWebImage的使用 1.依赖的框架 * ImageIO.framework * MapKit.framework 2.UIImageView下载图片需要的头文件:UIImageView+W ...

  9. php延迟加载的示例

    class a{ ; ; //public $d = 5; public function aa(){ echo self::$b; } public function cc(){ echo stat ...

  10. [转]glew, glee与 gl glu glut glx glext的区别和关系

    原文地址:http://blog.csdn.net/delacroix_xu/article/details/5881942 因为也是初接触,所以就当了解,等深入学习后再回顾这篇文章观点. GLEW是 ...