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

我决定弄清Protobuf,flatbuffers和Cap'n proto中哪一个对我的应用程序来说是最好/最快的序列化。在我的情况下,通过网络发送某种字节/字符数组(我序列化为该格式的原因)。因此,我对所有三个字符串(包括字符串,浮点数和整数)进行了序列化和反序列化,从而实现了简单的实现。这给了意想不到的结果:Protobuf是最快的。我会称它们为意料之外的,因为cap'n proto和flatbuffs都“声称”是更快的选择。在接受这一点之前,我想看看我是否以某种方式一意孤行地欺骗了我的代码。如果我不作弊,我想知道为什么protobuf更快(确切地说为什么可能是不可能的)。消息是否可以包含cap'n proto和faltbuffers以使其真正发光?

我的时间

平面缓冲所花费的时间:14162微秒
捕获所需的时间:60259微秒
Protobuf花费的时间:12131微秒
(显然,这些都取决于我的机器,但是重要的是相对时间)

平面缓冲区代码

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;
}

验证码原型

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;

}

protobuf代码

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;
}

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

lmy87488860 回答:Protobuf vs Flatbuffers vs Cap'n proto哪个更快?

在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将消息写到管道中并读回。 (为公平起见,您还需要使protobuf基准也可以写入/读取管道。)

(我是Cap'n Proto和Protobuf v2的作者。我对FlatBuffers不熟悉,因此无法评论该代码是否存在任何类似问题...)


基准测试

我花了很多时间对Protobuf和Cap'n Proto进行基准测试。我在此过程中了解到的一件事是,您可以创建的大多数简单基准测试不会给您真实的结果。

首先,给定正确的基准案例,任何序列化格式(甚至JSON)都可以“获胜”。根据内容的不同,不同的格式会有很大的不同。是沉重的字符串,沉重的数字还是沉重的对象(即具有较深的消息树)?不同的格式在这里有不同的优势(例如,Cap'n Proto在数字方面非常出色,因为它根本无法转换它们; JSON在它们方面非常糟糕)。您的邮件大小是难以置信的短,中等长度还是很大?短消息将主要执行设置/拆卸代码,而不是正文处理(但是设置/拆卸很重要-有时在现实中的用例会涉及很多小消息!)。非常大的消息将破坏L1 / L2 / L3缓存,并向您提供更多有关内存带宽的信息,而不是解析复杂性(但这再次很重要-一些实现比其他实现更易于缓存)。

即使考虑了所有这些问题,您仍然遇到另一个问题:在循环中运行代码实际上并不能告诉您它在现实世界中的性能。在紧密循环中运行时,指令高速缓存保持高温,并且所有分支都变得高度可预测。因此,繁重的分支序列化(如protobuf)将大大降低分支成本,而代码占用量大的序列化(再次如protobuf)也将获得优势。这就是为什么微基准测试仅对将代码与其他版本本身进行比较(例如,测试次要优化)而不对将完全不同的代码库进行比较真正有用。为了弄清其中任何一个在现实世界中的表现,您需要端到端衡量一个现实世界的用例。但是...说实话,这很难。很少有人有时间根据两种不同的序列化来构建整个应用程序的两个版本,以查看哪个版本胜出...

本文链接:https://www.f2er.com/2391197.html

大家都在问