【问题标题】:Performance implications of using a flatter schema使用更扁平模式的性能影响
【发布时间】:2018-06-08 00:15:29
【问题描述】:

我正在使用 FlatBuffers (C++) 来存储有关文件的元数据信息。这包括 EXIF、IPTC、GPS 和各种其他元数据值。

在我当前的模式中,我有一个相当规范化的定义,上面列出的每个组都有自己的表。根表只包含每个子表的属性。

基本示例:

table GPSProperties {
  latitude:double;
  longitude:double;
}

table ContactProperties {
  name:string;
  email:string;
}

table EXIFProperties {
  camera:string;
  lens:string;
  gps:GPSProperties;
}

table IPTCProperties {
  city:string;
  country:string;
  contact:ContactProperties;
}

table Registry {
 exifProperties:EXIFProperties;
 iptcProperties:IPTCProperties;
}

root_type Registry;

这可行,但是构建缓冲区时的嵌套限制开始使代码变得非常混乱。同样,将属性分解为单独的表只是为了在架构中清晰。

我正在考虑将整个架构“扁平化”到一个表中,但我想知道这样做是否会影响性能或内存。这个单一的表可能有几百个字段,但大多数都是空的。

建议:

table Registry {
  exif_camera:string;
  exif_lens:string;
  exif_gps_latitude:double;
  exif_gps_longitude:double;
  iptc_city:string;
  iptc_country:string;
  iptc_contact_name:string;
  iptc_contact_email:string;
}

root_type Registry;

由于未设置或设置为其默认值的属性不会占用任何内存,我倾向于相信扁平化架构可能不会有问题。但我不确定。

(请注意,性能是我最关心的问题,其次是内存使用情况。规范化模式的性能非常好,但我认为扁平化模式确实有助于我清理代码库。)

【问题讨论】:

    标签: c++ flatbuffers


    【解决方案1】:

    你应该首先清楚的基本知识:

    1. 每个表的顶部都有一个 vtable,它告诉可以找到表的每个字段的偏移量。如果一个表的字段太多,不管你存不存数据,这个vtable都会变大。

    2. 如果您尝试创建表的层次结构,则会创建额外的 vtable,并且还会增加设计的间接成本。

    3. 如果在多个对象中存储了类似的数据,则 vtables 也是共享的。就像您正在创建仅使用 exif_camera 变量的对象!

    因此,这取决于您的数据是否庞大且异构,是否使用更有条理的层次结构。但是,如果您的数据是同质的,则更喜欢扁平表。

    【讨论】:

    • 我创建的每个缓冲区都代表一个文件,每个缓冲区都独立存储在数据库条目中,因此我认为没有任何“共享”在进行。在我提出的“平面模式”中,最终缓冲区中只有一个“注册表”对象,所以我认为我不会从任何 v-table 共享中受益。我做了一些快速测试,创建了一个包含 300 个 int 类型字段的表,并随机设置了其中的一小部分。在这样一个基本测试中,事情看起来“还可以”。 (即:内存使用量与我预期的差不多,但也许 300 的质量不如“许多字段”。)
    【解决方案2】:

    由于您的大部分数据都是字符串,因此这两种设计的大小和速度将非常相似,因此您可能应该根据软件工程的角度来选择更适合您的数据。

    也就是说,平面版本在大小上可能会稍微更有效(更少的 vtables),而且访问速度肯定会更快(尽管再次强调,这是微不足道的,因为它主要是字符串数据)。

    平面版本效率较低的唯一方法是,如果您将大量数据存储在一个缓冲区中,其中设置的字段在每个表之间差异很大。那么non-flat版本可能会产生更多的vtable共享。

    在非平面版本中,如果字段不太可能发生变化,GPSProperties 之类的表可能是 struct,这样会更有效。

    【讨论】:

    • 如上所述,每个缓冲区与任何其他缓冲区分开存储,因为它代表单个文件的元数据。我在上面使用了图像元数据,但也可能有 PDF、音频、视频、文件系统、XMP 等元数据的字段。绝对超过100,但不超过“几百”。大多数都是空的,因为它们依赖于文件类型。 (即:图像文件将填充 EXIF 和 IPCT 相关字段,但将所有其他字段保留为默认值。)缓冲区将只有一个表作为其根,并且该数据将存储在 SQLite 列中。
    • 啊,我没有得到 100 多个字段。如果未与其他对象共享,则每个未使用字段的成本为 2 个字节。只有在使用/未使用结构方面碰巧与其他表重叠时,非平面版本才能节省这一点。如果您的数据非常稀疏,您是否考虑过联合向量?老实说,如果上述方法对于超稀疏数据仍然效率低下,Protocol Buffers 可以更好地处理超稀疏数据。
    • 哦,哇...我错过了那个小细节,我的测试没有注意到它,因为我总是在构造函数中设置一个参数后检查缓冲区的大小。设置第 1 个和第 100 个字段会产生更大的缓冲区。我认为教程中的这一行:"...这也意味着您不必担心添加大量仅在少数实例中使用的字段,因为它不会使缓冲区膨胀如果未使用。” 应该稍微澄清一下。
    【解决方案3】:

    这个表可能有几百个字段,但大多数字段会 为空。

    性能成本可能很小,您不会注意到,但对我来说,您上面的报价是决定使用哪种设计的摇摆因素。

    当其他人在谈论 vtables 的成本时;我根本不会担心这个。每个类有一个 vtable,每次运行准备一次,不会很贵。 然而,拥有 100 个空且未使用的字符串将非常昂贵(内存使用方面)并且会消耗您创建的每个对象;此外,读取您的字段将变得更加复杂,因为您不能再假设您读取的类的所有数据都在那里。

    如果大多数/所有字段始终存在,那么我可以看到制作单个课程的吸引力;但他们不是。

    【讨论】:

    • FlatBuffer 没有被取消归档到相应的 C++ 类中。相反,FlatBuffer 只是用于存储。当我需要元数据属性的值时,会从数据库 (SQLite) 中读取 FlatBuffer,然后将我需要的值直接读入一个非常基本的结构中,该结构最多保存一个字符串、整数、双精度或布尔值。这是通过 oversized switch 语句完成的,该语句可以将内部枚举值(属性标签)映射到 FlatBuffer 中的相应字段/getter。
    猜你喜欢
    • 2017-10-31
    • 2016-02-04
    • 2010-09-30
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2011-07-10
    • 1970-01-01
    相关资源
    最近更新 更多