C++ 使用更平坦模式的性能影响

C++ 使用更平坦模式的性能影响,c++,flatbuffers,C++,Flatbuffers,我使用FlatBuffers(C++)来存储关于文件的元数据信息。这包括EXIF、IPTC、GPS和各种其他元数据值 在我当前的模式中,我有一个相当规范化的定义,上面列出的每个组都有自己的表。根表只包含每个子表的属性 基本示例: table GPSProperties { latitude:double; longitude:double; } table ContactProperties { name:string; email:string; } table EXIFP

我使用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;
由于未设置或设置为默认值的属性不会占用任何内存,因此我倾向于认为扁平化模式可能不是问题。但我不确定


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

基本知识您应该首先了解:

  • 每个表的顶部都有一个vtable,它告诉我们可以找到表中每个字段的偏移量。如果一个表中有太多的字段,不管您是否存储数据,这个vtable都会变得巨大

  • 如果您试图创建表的层次结构,那么您将创建额外的vtable,并为设计增加间接成本

  • 此外,如果在多个对象中存储类似的数据,则vtables也会共享。。就像在创建对象时只使用exif_摄影机变量一样


  • 因此,这取决于您的数据是否将是巨大的和异构的,请使用更有组织的层次结构。但是,如果您的数据是同质的,那么您更喜欢扁平的表。

    因为您的大多数数据都是字符串,所以这两种设计的大小和速度都非常相似,因此您可能应该从软件工程的角度来选择对您更有利的设计

    这就是说,平面版本可能会在大小上稍有效率(vtables更少),而且访问速度肯定会更快(不过,考虑到它主要是字符串数据,这也是微不足道的)

    平面版本效率较低的唯一方法是将大量数据存储在一个缓冲区中,每个表之间设置的字段差异很大。然后,非平面版本可能会生成更多vtable共享

    在非平面版本中,如果字段不太可能更改,则像
    gpsprroperties
    这样的表可以是
    struct
    ,这样会更有效

    这个表可以有几百个字段,但大多数字段都可以 空着

    性能成本可能很小,你不会注意到,但你上面的报价,对我来说,是影响使用哪种设计的因素

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


    如果大部分/所有的领域都在那里,那么我可以看到制作一门课程的吸引力;但事实并非如此。

    我创建的每个缓冲区都代表一个文件,每个缓冲区都独立地存储在一个数据库条目中,因此我认为不会有任何“共享”。在我提出的“平面模式”中,最终缓冲区中只有一个“注册表”对象,因此我认为我不会从任何v表共享中受益。我做了一些快速测试,创建了一个包含300个
    int
    类型字段的表,并随机设置了少量字段。在这样一个基本的测试中,情况似乎“正常”。(即:内存使用量与我预期的差不多,但可能300的质量不如“许多字段”。)如上文所述,每个缓冲区与任何其他缓冲区分开存储,因为它代表单个文件的元数据。我在上面使用了图像元数据,但也可能有用于PDF、音频、视频、文件系统、XMP等元数据的字段。肯定超过100,但不超过“几百”。大多数都是空的,因为它们依赖于文件类型。(即:图像fie将填充EXIF和IPCT相关字段,但保留所有其他字段的默认值。)缓冲区将只有一个表作为其根,数据将存储在SQLite列中。啊,我没有得到100多个字段。如果未与其他对象共享,则每个未使用字段的开销为2字节。只有在使用/未使用的结构与其他表重叠的情况下,非平面版本才能节省这一成本。如果您的数据是超稀疏的,您是否考虑过联合向量?老实说,如果上面提到的对于超稀疏数据来说效率仍然很低,那么协议缓冲区处理超稀疏数据的效果会更好。。。我错过了那个小细节,我的测试没有抓住它,因为我总是在构造函数中设置一个以上的参数后检查缓冲区的大小。设置第1个和第100个字段会产生更大的缓冲区。我认为教程中的这行是:“……这也意味着,您不必担心只在少量实例中添加很多字段,因为如果未使用,它不会膨胀缓冲区。”应该有点澄清。FlatBuffer没有被存档到相应的C++类中。相反,FlatBuffer只是用于存储。当我需要metada的值时