美文网首页
FlatBuffers:flatc源码简析

FlatBuffers:flatc源码简析

作者: SunnyZhou1024 | 来源:发表于2020-09-04 16:53 被阅读0次

FlatBuffers 是Google推出的一个跨平台、跨语言的序列化和反序列化库,主要用于游戏以及对性能要求较高的系统中,例如RPC框架、保存端测推理的模型文件等(如TFLite)。端测不同于服务器,内存和算力等资源相对于服务器十分有限,想要缩短整个推理的时间和内存消耗,模型加载的阶段也需要考虑。FlatBuffers可以只使用一块内存进行解析,恰好满足这些要求。其使用步骤如下:

  1. 下载源码编译得到一个编译该库指定的IDL(Interface Definition Language)所定义的Schema的编译器flatc;
  2. 按照IDL的语法编写Schema;
  3. 使用第一步编译出的flatc编译第二步写出的Schema,得到对应语言的序列化和反序列化接口;
  4. 使用第三步得到的接口进行序列化和反序列化。

具体使用方法参考官方文档即可。一般情况下,我们只需要知道FlatBuffers这个库是怎么使用的就够了,并不需要知道我们编写的Schema是如何被编译生成对应语言的接口的。

但是有意思的是,FlatBuffers包含了两个我感兴趣的东西:一个是它序列化数据的时候的思想,之前在FlatBuffer内部解析原理简介一文中有做过总结;另一个就是它的编译器。

俗话说麻雀虽小五脏俱全,作为一个编译器,虽然相比于GCC、LLVM等它非常简单,但是它的代码中对于词法分析、语法分析以及代码生成等都有体现。

1. 工作流程

flatc的入口位于flatbuffers/src/flatc_main.cpp中,其具体工作流程如图1所示。整个工作流程可以分为三部分:

  1. 解析命令行、初始化;
  2. 对源文件进行解析,涉及词法分析和语法分析,这两个阶段是合并在一起的;
  3. 目标语言的代码生成。
图1 flatbuffers workflow sequence
首先,flatc开辟了一个结构体Generator的数组空间,该结构体如下所示。
  struct Generator {
    typedef bool (*GenerateFn)(const flatbuffers::Parser &parser,
                               const std::string &path,
                               const std::string &file_name);
    typedef std::string (*MakeRuleFn)(const flatbuffers::Parser &parser,
                                      const std::string &path,
                                      const std::string &file_name);

    GenerateFn generate;
    const char *generator_opt_short;
    const char *generator_opt_long;
    const char *lang_name;
    bool schema_only;
    GenerateFn generateGRPC;
    flatbuffers::IDLOptions::Language lang;
    const char *generator_help;
    MakeRuleFn make_rule;
  };

后续通过匹配用户命令行的参数选生成哪些语言的API,例如下面的结构体实例是用于生成C++ API的,当用户的命令中存在-c或者--cpp,最终就会有C++的API生成。

    { flatbuffers::GenerateCPP, "-c", "--cpp", "C++", true,
      flatbuffers::GenerateCppGRPC, flatbuffers::IDLOptions::kCpp,
      "Generate C++ headers for tables/structs", flatbuffers::CPPMakeRule     
    }

紧接着,flatc解析命令行参数,解析完成后便开始编译。FlatCompiler对源文件进行加载,之后委托Parser进行解析,DoParse()就是整个解析的核心。

源文件解析完成后,通过查看Generator数组,再相应的委托BaseGenerator对应的子类进行代码生成,例如要生成C++代码就委托CppGenerator

2. 词法分析

词法分析是每个编译器进行编译的第一个阶段,词法分析的目的就是扫描从源码文件中读入的字符串,并将它们分成一个一个的Token,以便后面做语法分析。
虽然词法分析和语法分析是编译过程中的两个阶段,但通常情况下,它们之间并不是完全独立的。语法分析并不会等待词法分析将整个源文件都分成一个个Token才开始工作,语法分析会以命令的方式要求词法分析器提供一个一个的Token。

在FlatBuffers flatc中,词法分析和语法分析的代码都是在类Parser中完成的,其中Next()方法负责词法分析,每一次调用,它就会从当前光标开始扫描,然后返回下一个Token。Parser中有一块用于存放从文件中读入的字符串的缓存source_,它是一块连续的内存区域,可以看做是一个存放字符的数组;还有一个光标cursor_用于表示当前扫描位置。

Parser的语法分析器(其实也就是一个函数Parse())通过调用Next()获得一个一个的Token进行语法分析。在Next()方法中光标cursor_source_上从左向右滑动,并返回一个一个Token。Parse()负责分析。例如在下面的例子,例子所示为一个名为Monster的结构体的定义。

table Monster {
  pos:Vec3;
  mana:short = 150;
  hp:short = 100;
  name:string;
  friendly:bool = false (deprecated, priority: 1);
  inventory:[ubyte];
  color:Color = Blue;
  test:Any;
}

一开始,光标位于最开始得字符t,然后开始滑动,直到划过table这个词,遇见第一个空格,根据规则,此时table被识别成一个Token,因此Next()函数便将这个Token返回给调用者Parse()Parse()在得到该Token后,识别到它是一个关键字,它后面应该需要跟上的是一个标识符,因此它再次调用Next()去获取下一个Token,并判断这个Token是不是所期望的标识符。如果得到的并不是一个标识符,那么说明语法有误,终止编译并报错。如果此时得到的Token是标识符,那么根据要求,需要紧接着的是又花括号包含的成员定义,因此Parse()在此调用Next()去获取下一个Token。语法分析器和词法分析器就是这样反复交互,直到整个文件扫描分析结束或者出错终止。

这个Next()的逻辑如图2所示(状态图更合适,但是奈何手头没有适合画状态图的工具)。

图2 Parser::Next()

3. 语法分析

通常情况下,一般编译器的语法分析器会构造一颗解析树,并将这颗解析树传递给后续的编译阶段进行进一步处理。但是由于flatc编译的是接口描述语言,语言本身并不复杂也不包含计算,并且最终生成的是其他语言的代码,并不是直接运行的机器码,因此它只需要解析的同时提取到每个定义的结构的名字、初始值等信息即可。
还是以上面的代码为例,当解析Monster的时候,Parser会将Monster的信息保存在一个名叫struct_的数组中。后续读取此数组便可以获取到用户定义的信息进行代码生成。

整个解析过程如图3所示。


图3 Parser::DoParse()

4. 总结

看这部分的代码最大的收获就是对于如何解析一个文件豁然开朗,很多需要文本处理的软件中都有着编译器前端的部分影子。甚至是正则表达,其实仔细想想,不就是一个词法分析器么?

本文首发于个人微信公众号TensorBoy,微信扫描上方二维码或者微信搜索TensorBoy并关注,及时获取最新文章。C++ | Python | Linux | 原理 | 源码,有一起玩耍的么?

5. References

[1] http://google.github.io/flatbuffers/index.html
[2] https://github.com/google/flatbuffers

相关文章

  • FlatBuffers:flatc源码简析

    FlatBuffers 是Google推出的一个跨平台、跨语言的序列化和反序列化库,主要用于游戏以及对性能要求较高...

  • mybatis-spring解析

    1、概述 原生Mybatis源码简析(上)原生Mybatis源码简析(下)在介绍原生Mybatis源码简析文章中,...

  • 是时候看一下flatbuffer

    1、flatbuffers简介 2、flatbuffers VS JSON 3、flatbuffers 使用 1、...

  • Flink自定义StreamOperator

    在上一篇StreamOperator源码简析从源码角度分析了StreamOperator以及其实现类,此篇幅主要分...

  • FlatBuffers反序列化过程

    FlatBuffers简介FlatBuffers Schema解析FlatBuffers序列化过程FlatBuff...

  • FlatBuffers序列化过程

    FlatBuffers简介FlatBuffers Schema解析FlatBuffers序列化过程FlatBuff...

  • FlatBuffers Schema解析

    FlatBuffers简介FlatBuffers Schema解析FlatBuffers序列化过程FlatBuff...

  • FlatBuffers简介

    FlatBuffers简介FlatBuffers Schema解析FlatBuffers序列化过程FlatBuff...

  • OkHttp源码简析

    Android平台有很多优秀的开源库,OkHttp绝对是其中的佼佼者,它是Square出品的一个网络通讯库,功能强...

  • HashMap源码简析

    说到HashMap相信大家并不陌生,这是一个非常常用的以键值对形式存储的数据结构,但是对其内部原理可能不是很了解,...

网友评论

      本文标题:FlatBuffers:flatc源码简析

      本文链接:https://www.haomeiwen.com/subject/aizojktx.html