使用 boost::asio::ip::tcp::iostream 的低带宽性能

Low bandwidth performance using boost::asio::ip::tcp::iostream

我写了一个小测试程序,使用boost::asio::ip::tcp::iostream传输大约38MiB的数据:

#include <boost/archive/text_iarchive.hpp>
#include <boost/archive/text_oarchive.hpp>
#include <boost/asio.hpp>
#include <boost/serialization/export.hpp>
#include <boost/serialization/shared_ptr.hpp>
#include <boost/serialization/vector.hpp>
#include <chrono>
#include <iostream>
#include <sstream>
#include <string>
#include <thread>

using namespace std;

class Message {
public:
    Message() {
    }

    virtual ~Message() {
    }

    string text;
    std::vector<int> bigLoad;

private:
    friend class boost::serialization::access;

    template <class Archive>
    void serialize(Archive &ar, const unsigned int version) {
        ar &text;
        ar &bigLoad;
    }
};

BOOST_CLASS_EXPORT(Message)

void runClient() {
    // Give server time to startup
    this_thread::sleep_for(chrono::milliseconds(3000));

    boost::asio::ip::tcp::iostream stream("127.0.0.1", "3000");
    // const boost::asio::ip::tcp::no_delay option(true);
    // stream.rdbuf()->set_option(option);

    Message message;

    stringstream ss;
    ss << "Hello World!";
    message.text = ss.str();

    int items = 10000000;
    int size = sizeof(int) * items;

    std::cout << "Size in Byte = " << size << endl;
    std::cout << "Size in KiB = " << size / 1024 << endl;
    std::cout << "Size in MiB = " << size / 1024 / 1024 << endl;

    for (int i = 0; i < items; i++)
        message.bigLoad.push_back(i);

    boost::archive::text_oarchive archive(stream);

    cout << "Client start to send message" << endl;
    try {
        archive << message;
    } catch (std::exception &ex) {
        cout << ex.what() << endl;
    }
    cout << "Client send message" << endl;

    stream.close();
    cout << "Client shutdown" << endl;
}

void handleIncommingClientConnection(boost::asio::ip::tcp::acceptor &acceptor) {
    boost::asio::ip::tcp::iostream stream;
    // const boost::asio::ip::tcp::no_delay option(true);
    // stream.rdbuf()->set_option(option);

    acceptor.accept(*stream.rdbuf());

    boost::archive::text_iarchive archive(stream);

    while (true) {
        try {
            Message message;
            archive >> message;
            cout << message.text << endl;
        } catch (std::exception &ex) {
            cout << ex.what() << endl;

            if (stream.eof()) {
                cout << "eof" << endl;
                stream.close();
                cout << "Server: shutdown client handling..." << endl;
                break;
            } else
                throw ex;
        }
    }
}

void runServer() {
    boost::asio::io_service ios;
    boost::asio::ip::tcp::endpoint endpoint = boost::asio::ip::tcp::endpoint(boost::asio::ip::tcp::v4(), 3000);
    boost::asio::ip::tcp::acceptor acceptor(ios, endpoint);

    handleIncommingClientConnection(acceptor);
}

template <typename TimeT = std::chrono::milliseconds>
struct measure {
    template <typename F, typename... Args>
    static typename TimeT::rep execution(F &&func, Args &&... args) {
        auto start = std::chrono::steady_clock::now();
        std::forward<decltype(func)>(func)(std::forward<Args>(args)...);
        auto duration = std::chrono::duration_cast<TimeT>(std::chrono::steady_clock::now() - start);
        return duration.count();
    }
};

void doIt() {
    thread clientThread(runClient);
    thread serverThread(runServer);

    clientThread.join();
    serverThread.join();
}

int main(int argc, char **argv) {
    std::cout << measure<std::chrono::seconds>::execution(doIt) << std::endl;

    return 0;
}

程序在发布模式下的输出如下所示:

Size in Byte = 40000000
Size in KiB = 39062
Size in MiB = 38
Client start to send message
Client send message
Client shutdown
Hello World!
input stream error
eof
Server: shutdown client handling...
148

传输 38 MB 用了 148 秒(超过 2 分钟)。我可以将数据复制到 USB 记忆棒并手动移交,这比 boost::asio 更快。

有什么方法可以提高带宽性能吗?

您的时间可能浪费在序列化 to/from 文本上。

对我来说,删除二进制存档确实将速度从 80Mbit/s 提高到 872MBit/s:

Client start to send message
Client send message
Client shutdown
Received: Hello World!
3

以秒为单位的总时间减少到3s,刚好是初始睡眠:)

概念验证 Live On Coliru

#include <boost/archive/binary_iarchive.hpp>
#include <boost/archive/binary_oarchive.hpp>
#include <boost/archive/text_iarchive.hpp>
#include <boost/archive/text_oarchive.hpp>
#include <boost/asio.hpp>
#include <boost/serialization/export.hpp>
#include <boost/serialization/shared_ptr.hpp>
#include <boost/serialization/vector.hpp>
#include <chrono>
#include <iostream>
#include <sstream>
#include <string>
#include <thread>

using namespace std;

class Message {
  public:
    Message() {}

    virtual ~Message() {}

    string text;
    std::vector<int> bigLoad;

  private:
    friend class boost::serialization::access;

    template <class Archive> void serialize(Archive &ar, const unsigned int /*version*/) {
        ar & text & bigLoad;
    }
};

BOOST_CLASS_EXPORT(Message)

void runClient() {
    // Give server time to startup
    this_thread::sleep_for(chrono::seconds(1));

    boost::asio::ip::tcp::iostream stream("127.0.0.1", "3000");
    const boost::asio::ip::tcp::no_delay option(false);
    stream.rdbuf()->set_option(option);

    Message message;

    stringstream ss;
    ss << "Hello World!";
    message.text = ss.str();

    int items = 8 << 20;

    for (int i = 0; i < items; i++)
        message.bigLoad.push_back(i);

    boost::archive::binary_oarchive archive(stream);

    cout << "Client start to send message" << endl;
    try {
        archive << message;
    } catch (std::exception &ex) {
        cout << ex.what() << endl;
    }
    cout << "Client send message" << endl;

    stream.close();
    cout << "Client shutdown" << endl;
}

void handleIncommingClientConnection(boost::asio::ip::tcp::acceptor &acceptor) {
    boost::asio::ip::tcp::iostream stream;
    // const boost::asio::ip::tcp::no_delay option(false);
    // stream.rdbuf()->set_option(option);

    acceptor.accept(*stream.rdbuf());

    boost::archive::binary_iarchive archive(stream);

    {
        try {
            Message message;
            archive >> message;
            cout << "Received: " << message.text << endl;
        } catch (std::exception &ex) {
            cout << ex.what() << endl;

            if (stream.eof()) {
                cout << "eof" << endl;
                stream.close();
                cout << "Server: shutdown client handling..." << endl;
                return;
            } else
                throw;
        }
    }
}

void runServer() {
    using namespace boost::asio;
    using ip::tcp;

    io_service ios;
    tcp::endpoint endpoint = tcp::endpoint(tcp::v4(), 3000);
    tcp::acceptor acceptor(ios, endpoint);

    handleIncommingClientConnection(acceptor);
}

template <typename TimeT = std::chrono::milliseconds> struct measure {
    template <typename F, typename... Args> static typename TimeT::rep execution(F &&func, Args &&... args) {
        auto start = std::chrono::steady_clock::now();
        std::forward<decltype(func)>(func)(std::forward<Args>(args)...);
        auto duration = std::chrono::duration_cast<TimeT>(std::chrono::steady_clock::now() - start);
        return duration.count();
    }
};

void doIt() {
    thread clientThread(runClient);
    thread serverThread(runServer);

    clientThread.join();
    serverThread.join();
}

int main() { std::cout << measure<std::chrono::seconds>::execution(doIt) << std::endl; }

注意:

有一件事是 "lost",旧版本的代码也没有真正支持它:直接接收多个档案。

您可能想要安装某种成帧协议。参见示例

我在这里发表了很多 "overhead of Boost Serialization" 篇文章:

  • how to do performance test using the boost library for a custom library
  • Boost C++ Serialization overhead