当前位置: 首页 > 知识库问答 >
问题:

Protobuf vs Flatbuffers vs Cap'n proto哪个更快?

沈宇定
2023-03-14

我决定找出Protobuf、Flatbuffers和Cap'n proto中的哪一个是我的应用程序最好/最快的序列化。在我的例子中,通过网络发送某种字节/字符数组(这就是我序列化为这种格式的原因)。因此,我对这三种方法都做了简单的实现,其中我对字符串、浮点和int进行了sealize和dezeralize。这带来了意想不到的结果:Protobuf是最快的。我会说他们出人意料,因为cap'n proto和flatbuffes都“声称”是更快的选择。在我接受这一点之前,我想看看我是否以某种方式在代码中进行了常规作弊。如果我没有作弊,我想知道protobuf为什么更快(确切的原因可能是不可能的)。这些信息会不会是为了模仿普罗托船长和福特布弗船长,让他们真正发光?

我的时间安排:

所用时间flatbuffers:14162微秒
所用时间capnp:60259微秒
所用时间protobuf:12131微秒
(显然这些取决于我的机器,但重要的是相对时间)

flatbuffer代码:

int main (int argc, char *argv[]){
    std::string s = "string";
    float f = 3.14;
    int i = 1337;

    std::string s_r;
    float f_r;
    int i_r;
    flatbuffers::FlatBufferBuilder message_sender;

    int steps = 10000;
    auto start = high_resolution_clock::now(); 
    for (int j = 0; j < steps; j++){
        auto autostring =  message_sender.CreateString(s);
        auto encoded_message = CreateTestmessage(message_sender, autostring, f, i);
        message_sender.Finish(encoded_message);
        uint8_t *buf = message_sender.GetBufferPointer();
        int size = message_sender.GetSize();
        message_sender.Clear();
        //Send stuffs
        //Receive stuffs
        auto recieved_message = GetTestmessage(buf);

        s_r = recieved_message->string_()->str();
        f_r = recieved_message->float_();
        i_r = recieved_message->int_(); 
    }
    auto stop = high_resolution_clock::now(); 
    auto duration = duration_cast<microseconds>(stop - start); 
    cout << "Time taken flatbuffer: " << duration.count() << " microseconds" << endl;
    return 0;
}

cap'n原型代码:

int main (int argc, char *argv[]){
    char s[] = "string";
    float f = 3.14;
    int i = 1337;

    const char * s_r;
    float f_r;
    int i_r;
    ::capnp::MallocMessageBuilder message_builder;
    Testmessage::Builder message = message_builder.initRoot<Testmessage>();

    int steps = 10000;
    auto start = high_resolution_clock::now(); 
    for (int j = 0; j < steps; j++){  
        //Encodeing
        message.setString(s);
        message.setFloat(f);
        message.setInt(i);

        kj::Array<capnp::word> encoded_array = capnp::messageToFlatArray(message_builder);
        kj::ArrayPtr<char> encoded_array_ptr = encoded_array.asChars();
        char * encoded_char_array = encoded_array_ptr.begin();
        size_t size = encoded_array_ptr.size();
        //Send stuffs
        //Receive stuffs

        //Decodeing
        kj::ArrayPtr<capnp::word> received_array = kj::ArrayPtr<capnp::word>(reinterpret_cast<capnp::word*>(encoded_char_array), size/sizeof(capnp::word));
        ::capnp::FlatArrayMessageReader message_receiver_builder(received_array);
        Testmessage::Reader message_receiver = message_receiver_builder.getRoot<Testmessage>();
        s_r = message_receiver.getString().cStr();
        f_r = message_receiver.getFloat();
        i_r = message_receiver.getInt();
    }
    auto stop = high_resolution_clock::now(); 
    auto duration = duration_cast<microseconds>(stop - start); 
    cout << "Time taken capnp: " << duration.count() << " microseconds" << endl;
    return 0;

}

原型代码:

int main (int argc, char *argv[]){
    std::string s = "string";
    float f = 3.14;
    int i = 1337;

    std::string s_r;
    float f_r;
    int i_r;
    Testmessage message_sender;
    Testmessage message_receiver;
    int steps = 10000;
    auto start = high_resolution_clock::now(); 
    for (int j = 0; j < steps; j++){
        message_sender.set_string(s);
        message_sender.set_float_m(f);
        message_sender.set_int_m(i);
        int len = message_sender.ByteSize();
        char encoded_message[len];
        message_sender.SerializeToArray(encoded_message, len);
        message_sender.Clear();

        //Send stuffs
        //Receive stuffs
        message_receiver.ParseFromArray(encoded_message, len);
        s_r = message_receiver.string();
        f_r = message_receiver.float_m();
        i_r = message_receiver.int_m();
        message_receiver.Clear();

    }
    auto stop = high_resolution_clock::now(); 
    auto duration = duration_cast<microseconds>(stop - start); 
    cout << "Time taken protobuf: " << duration.count() << " microseconds" << endl;
    return 0;
}

不包括消息定义文件,因为它们很简单,很可能与此无关。

共有1个答案

岳正阳
2023-03-14

在Cap'n Proto中,您不应该对多条消息重复使用MessageBuilder。按照您编写代码的方式,循环的每次迭代都会使消息变得更大,因为您实际上是在添加现有消息,而不是开始新消息。为了避免每次迭代都分配内存,您应该向MallocMessageBuilder的构造函数传递一个临时缓冲区。可以在循环外分配一次暂存缓冲区,但每次循环时都需要创建一个新的MallocMessageBuilder。(当然,大多数人不需要临时缓冲区,只需要让MallocMessageBuilder自己分配,但是如果您在这个基准中选择了这个路径,那么您还应该更改Protobuf基准,为每个迭代创建一个新的消息对象,而不是重用单个对象。)

此外,您的Cap'n Proto代码正在使用capnp::MessageToFlatArray(),它分配了一个全新的缓冲区来将消息放入并复制整个消息。这不是使用Cap'n Proto的最有效方法。通常,如果您将消息写入文件或套接字,您将直接从消息的原始备份缓冲区写入,而不必复制此副本。试着这样做:

kj::ArrayPtr<const kj::ArrayPtr<const capnp::word>> segments =
    message_builder.getSegmentsForOutput();

// Send segments
// Receive segments

capnp::SegmentArrayMessageReader message_receiver_builder(segments);

或者,为了让事情变得更现实,您可以将消息写入管道并读取回来,使用capnp::WriteMessageToFd()capnp::StreamFdMessageReader。(为了公平起见,您还需要使原型基准写入/从管道读取。)

(我是普罗托船长和普罗托布夫v2的作者。我不熟悉FlatBuffers,所以我不能评论该代码是否有任何类似的问题...(

我花了很多时间对Protobuf和Cap'n Proto进行基准测试。在这个过程中我学到的一件事是,你能创建的最简单的基准不会给你现实的结果。

首先,任何序列化格式(即使是JSON)都可以在正确的基准情况下“获胜”。根据内容的不同,不同的格式将执行非常非常不同的操作。它是字符串重、数字重还是对象重(即具有较深的消息树)?不同的格式在这里有不同的优势(例如,Cap'nproto非常擅长数字,因为它根本不转换数字;JSON非常不擅长数字)。您的邮件大小是非常短、中等长度还是非常大?短消息主要是执行设置/拆卸代码,而不是正文处理(但是设置/拆卸很重要——有时现实世界的用例涉及很多小消息!)。非常大的消息将破坏一级/二级/三级缓存,并告诉您更多关于内存带宽的信息,而不是解析复杂性(但同样重要的是,有些实现比其他实现更容易缓存)。

即使考虑了所有这些因素,您仍然有另一个问题:在循环中运行代码实际上并不能告诉您它在现实世界中的执行情况。在紧密循环中运行时,指令缓存保持热状态,并且所有分支都具有高度可预测性。因此,大量使用分支的序列化(如protobuf)将使其分支成本大打折扣,而大量使用代码占用的序列化(同样……如protobuf)也将获得优势。这就是为什么微基准测试只在将代码与自身的其他版本进行比较(例如,测试次要优化)时才真正有用,而不是将完全不同的代码库相互比较。要了解这些在现实世界中的表现,您需要测量一个真实世界的端到端用例。但是老实说,这很难。很少有人有时间基于两种不同的序列化来构建整个应用程序的两个版本,看看哪一个版本会赢。。。

 类似资料:
  • 问题内容: 使用哪种更好或更方便: 要么 问题答案: 您是否完全需要类型属性?如果您使用的是HTML5,则不会。否则,是的。HTML 4.01和XHTML 1.0 根据需要指定属性,而HTML5具有可选属性,默认为。HTML5现在得到了广泛的实现,因此,如果您使用HTML5doctype,则是有效且不错的选择。 至于type属性中应该包含的内容,2006年注册的MIME类型旨在替代所有主要浏览器(

  • 我有一个案例,我有一组字段要在Solr中更新。我接收到的输入是映射的形式,键是字段名,值是更新的值。我有一个疑问,在这种情况下,我应该使用curl更新doc还是solrj,在那里我必须将映射转换为solrInputDocument,然后调用add命令。第一种方法会比第二种方法更快吗?

  • 问题内容: 我正在编写一种算法,在其中寻找一对值,这些值加在一起会导致我正在寻找另一个值。 我发现使用a 可以从O(n²)加速我的算法。后来我意识到我并没有真正使用我包含的值,因此就足够了。 我在Google上进行了幂搜索,但是在我的问题的标题中找不到这些方法的渐近运行时间的任何信息。 您能指出我应该在哪里寻找这些信息吗? 问题答案: 后来我意识到我并没有真正使用我包含的值,因此就足够了。 不仅是

  • 问题内容: 我已经阅读了很多有关每种方法的优缺点的信息,并且我知道委托通常是针对一个侦听器的,而通知则针对许多侦听器。问题在于性能。 我正在通过通知将麦克风的音频信号发送到另一个班级。我知道在这里我应该使用委托,但我的问题是:委托会更快吗?因为我可以看到我有一些帧速率问题(已减少),并且我想知道原因是否可能是使用通知 而不是 委托,还是没有关系? 问题答案: 代表的开销较少,因此执行速度更快。 但

  • 问题内容: 如果您的目标是测试MySQL列中是否存在字符串(类型为’varchar’,’text’,’blob’等),那么以下哪一项是更快/更有效/更好地使用,为什么? 或者,还有其他方法可以胜任这些方法吗? 与 问题答案: 正如kibibu在上述评论中指出的,FULLTEXT搜索绝对会更快。 但是 : 在我的测试中,它们的表现完全相同。它们都不区分大小写,并且通常会执行全表扫描,这在处理高性能M

  • 本文向大家介绍Zookeeper和Eureka哪个更好?,包括了Zookeeper和Eureka哪个更好?的使用技巧和注意事项,需要的朋友参考一下 Zookeeper和Eureka哪个更好? 1、CAP理论 一个分布式系统不可能同时很好的满足一致性,可用性和分区容错性这三个需求 C:数据一致性:保证所有数据都要同步 A:可用性:要保证任何时候请求数据都能够正常响应 P:分区容错性:当网络通信发生故

  • java.util.Objects类使用了许多新方法进行了扩展 对象#RequireNonNullElse 分别 中的对象#RequireNonNullElseget()。 如果第一个参数为非空,则返回第一个参数,否则返回非空的第二个参数或supplier.get()的非空值 但新功能与类中已存在的可选#orelse和可选#orelseget重叠 中的新方法与相应的方法之间的唯一区别是,suppl

  • 当开发一个需要我唯一识别手机用户的应用程序时。我希望得到一些关于使用手机UDID还是IMEI的建议。使用这两种方法有什么优点/缺点吗。如有任何意见,将不胜感激