【问题标题】:fastest c++ serialization?最快的 C++ 序列化?
【发布时间】:2011-04-07 22:41:11
【问题描述】:

我正在为 c++ 寻找一种非常快速的二进制序列化技术。我只需要序列化对象中包含的数据(没有指针等)。我希望它尽可能快。如果它是特定于 x86 硬件的,那是可以接受的。

我熟悉执行此操作的 C 方法。作为测试,我已经对几种技术进行了基准测试。我发现 C 方法比我实现的最佳 C++ 方法快 40%。

关于如何改进 C++ 方法(或执行此操作的库)的任何建议?内存映射文件有什么好的吗?

// c style writes
{
   #pragma pack(1)
   struct item
   {
      uint64_t off;
      uint32_t size;
   } data;
   #pragma pack

   clock_t start = clock();

   FILE* fd = fopen( "test.c.dat", "wb" );
   for ( long i = 0; i < tests; i++ )
   {
      data.off = i;
      data.size = i & 0xFFFF;
      fwrite( (char*) &data, sizeof(data), 1, fd );
   }
   fclose( fd );

   clock_t stop = clock();

   double d = ((double)(stop-start))/ CLOCKS_PER_SEC;
   printf( "%8.3f seconds\n", d );
}

大约 1.6 秒的测试 = 10000000

// c++ style ofstream writes

// define a DTO class
class test
{
public:
   test(){}

   uint64_t off;
   uint32_t size;

   friend std::ostream& operator<<( std::ostream& stream, const test& v );
};

// write to the stream
std::ostream& operator<<( std::ostream &stream,  const test& v )
{
   stream.write( (char*)&v.off, sizeof(v.off) );
   stream.write( (char*)&v.size, sizeof(v.size) );
   return stream;
}

{
   test data;

   clock_t start = clock();

   std::ofstream out;
   out.open( "test.cpp.dat", std::ios::out | std::ios::trunc | std::ios::binary );
   for ( long i = 0; i < tests; i++ )
   {
      data.off = i;
      data.size = i & 0xFFFF;
      out << data;
   }
   out.close();

   clock_t stop = clock();

   double d = ((double)(stop-start))/ CLOCKS_PER_SEC;
   printf( "%8.3f seconds\n", d );
}

大约 2.6 秒的测试 = 10000000

【问题讨论】:

  • 这不是序列化,而是内存转储。如果您的对象的内存布局发生变化,或者如果您从大端平台传递到小端平台,则它不起作用。
  • 那不等于代码。在 operator
  • @Matthieu:(来自维基百科)序列化:“序列化是将数据结构或对象转换为位序列以便可以存储在文件或内存缓冲区中的过程”。似乎符合使用该定义的条件。我真的不需要担心不同架构之间的互操作性。
  • @Dead:你不需要打包类,因为成员是分开写的。使用 C++ 可以像使用 C 结构一样使用类吗?
  • @Paul:“C++ 流非常慢。” - 但并非如此。在这种情况下,C++ 实现显然低于标准,但性能应该与 C 变体一样好。这也可能是由于iostreamstdio 中的默认缓冲设置不同。

标签: c++ serialization


【解决方案1】:

只有 非常 少数现实生活中的案例很重要。您只会序列化以使您的对象与某种外部资源兼容。磁盘、网络等。在资源上传输序列化数据的代码总是比序列化对象所需的代码慢几个数量级。如果您将序列化代码的速度提高一倍,那么您的整体操作速度不会超过 0.5%,无论是给予还是接受。这既不值得冒险也不值得付出努力。

测量三遍,切割一次。

【讨论】:

  • 直到你有大量的结构体,这就是序列化性能的瓶颈所在。传输二进制数据通常只是一个 syscall + memcpy(您可以一次传输多个对象)。
【解决方案2】:

如果要执行的任务确实是序列化,您可以查看 Google 的 Protocol Buffers。它们提供 C++ 类的快速序列化。该站点还提到了一些替代库,例如boost.serialization(当然,只是说明协议缓冲区在大多数情况下都优于它们;-)

【讨论】:

  • Protocol Buffers(尽管我很喜欢)并不是真正的序列化,它更多地用于消息传递。不同之处在于,对于协议缓冲区,您定义了一个 Message 类,而在序列化中没有中间表示。
  • 再想一想,您可以使用 protobuf 类在真实类中保存您的数据,这样您就可以在隐藏它的同时使用 protobuf 进行数据保存和编码/解码来自您的用户的事实。
  • 我写了我自己的类似 protobuf 的只有标头的东西:github.com/earonesty/qserial 它的速度和 proto 差不多,但是让它在奇怪的平台上工作要容易得多。
【解决方案3】:

C++ Middleware Writer 是序列化库的在线替代品。 在某些情况下是faster than the serialization library in Boost.

【讨论】:

    【解决方案4】:

    google flatbuffers,类似于协议缓冲区,但速度更快

    https://google.github.io/flatbuffers/

    https://google.github.io/flatbuffers/md__benchmarks.html

    【讨论】:

    • 他们的基准测试显示它比原始二进制读取慢四倍。我可以看到可能会有更好的情况。如果您正在处理大量不需要使用所有数据的数据,那么总体上可能会更快。
    【解决方案5】:

    好吧,如果您想要尽可能快的序列化,那么您可以编写自己的序列化类并为其提供方法来序列化每种 POD 类型。

    你带来的安全性越低,它运行得越快,调试起来就越困难,但是只有固定数量的内置,所以你可以枚举它们。

    class Buffer
    {
    public:
      inline Buffer& operator<<(int i); // etc...
    private:
      std::deque<unsigned char> mData;
    };
    

    我必须承认我不明白你的问题:

    • 您实际上想对序列化消息做什么?
    • 您要保存以备后用吗?
    • 您是否需要担心向前/向后兼容性?

    可能有比序列化更好的方法。

    【讨论】:

    • 我会将数据保存到磁盘。它只会加载到保存它的同一台机器上。我正在考虑在对象上放置一个版本号,以便能够更好地处理更改。如果您知道更好的方法,我很乐意听到。
    • 版本控制是必须的,否则你会被卡住。不过,您可以控制多少版本,因为对每个结构进行版本控制会使事情变得更加昂贵,并且拥有一个版本并不容易维护。我还建议从一个地方到另一个地方使用一些“同步”标记,也许还有一个 CRC 码来检查数据完整性(以防文件损坏)。我已经评论了 Thorsten77 关于 protobuf 的回答,看起来它可以帮助你很多。
    【解决方案6】:

    有没有什么方法可以利用不变的东西?

    我的意思是,您只是想尽可能快地运行“test.c.dat”,对吗?您可以利用文件在您的序列化尝试之间不会更改的事实吗?如果您尝试一遍又一遍地序列化同一个文件,您可以在此基础上进行优化。我可以让第一次序列化尝试花费与你相同的时间,再加上一点额外的时间来进行另一次检查,然后如果你尝试在相同的输入上再次运行序列化,我可以让我的第二次运行比第一次。

    我知道这可能只是一个精心设计的示例,但您似乎专注于让该语言尽快完成您的任务,而不是问“我需要再次完成这个任务吗?”这种方法的背景是什么?

    我希望这会有所帮助。

    -布莱恩·J·斯蒂纳尔-

    【讨论】:

    • 它将被用作配置数据库。我写的代码只是为了测试方法的开销。好主意。
    【解决方案7】:

    如果您使用的是 Unix 系统,则文件上的 mmap 做您想做的事情的方法。

    请参阅http://msdn.microsoft.com/en-us/library/aa366556(VS.85).aspx 以获取 Windows 上的等效项。

    【讨论】:

    • 那是我名单上的下一个。感谢您的确认
    【解决方案8】:

    很多性能将取决于内存缓冲区以及在写入磁盘之前如何填充内存块。还有一些技巧可以让标准 c++ 流更快一点,比如std::ios_base::sync_with_stdio (false);

    但是恕我直言,世界不需要序列化的另一个实现。以下是其他人认为您可能想要研究的一些内容:

    • Boost: 快速的各种 C++ 库,包括序列化
    • protobuf: 使用 C++ 模块快速跨平台、跨语言序列化
    • thrift: 灵活的跨平台、跨语言的 C++ 模块序列化

    【讨论】:

    • 向我展示一个序列化包,它在内存使用情况确定的受限环境中很有用,我将向您展示您将需要的唯一序列化包。在那之前,当每个人对序列化的要求相互矛盾的时候,说我们不需要另一个序列化包有点似是而非。
    • 我看了提升。它跳过各种循环来序列化任何对象,我只需要 POD。为什么要为您不需要的额外付费?
    • @Jay:如果您只需要对 POD 的支持,为什么不直接使用您的 C 方法?
    • 我希望这里有人想到了我没有想到的东西:(
    【解决方案9】:

    因为 I/O 最有可能成为瓶颈,所以紧凑格式可能会有所帮助。出于好奇,我尝试了以下 Colfer 方案,编译为 colf -s 16 C

        package data
    
        type item struct {
                off  uint64
                size uint32
        }
    

    ...具有可比较的 C 测试:

        clock_t start = clock();
    
        data_item data;
        void* buf = malloc(colfer_size_max);
    
        FILE* fd = fopen( "test.colfer.dat", "wb" );
        for ( long i = 0; i < tests; i++ )
        {
           data.off = i;
           data.size = i & 0xFFFF;
           size_t n = data_item_marshal( &data, buf );
           fwrite( buf, n, 1, fd );
        }
        fclose( fd );
    
        clock_t stop = clock();
    

    尽管序列大小比原始结构转储小 40%,但 SSD 上的结果非常令人失望。

        colfer took   0.520 seconds
        plain took    0.320 seconds
    

    由于生成的代码是pretty fast,您似乎不太可能通过序列化库赢得任何东西。

    【讨论】:

      【解决方案10】:

      您的 C 和 C++ 代码都可能(及时)受文件 I/O 支配。我建议在写入数据时使用内存映射文件,并将 I/O 缓冲留给操作系统。 Boost.Interprocess 可能是另一种选择。

      【讨论】:

      • 内存映射文件非常特定于操作系统。
      【解决方案11】:

      要真正回答这个问题,C++版本之所以慢,是因为它调用ostream.write的次数太多,导致大量不必要的状态检查。您可以创建一个简单的缓冲区并仅使用一个 write,您会看到不同之处。

      如果您的磁盘/网络真的足够快而不会成为瓶颈,flatbuffers capnproto 是您处理此问题的绝佳选择。

      否则,protobufxxx-compact...无论使用 varint 编码,都可能将这些数据序列化为原始大小的四分之一。 来自科学计算社区的HPS 对于这种高度结构化的数据也是一个很好的选择,并且由于其编码方案,在这种情况下可能是速度最快且消息大小最小的。

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 2014-05-15
        • 2013-08-12
        • 1970-01-01
        • 2010-10-08
        • 1970-01-01
        • 1970-01-01
        • 2010-12-27
        • 2017-12-07
        相关资源
        最近更新 更多