Reputation: 8142
I have written a small test program that uses boost::asio::ip::tcp::iostream
to transmit about 38 MiB of data:
#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;
}
The output of the program in release mode looks like this:
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
It took 148 seconds (more than 2 minutes) to transfer 38 MB. I could copy the data to a USB stick and hand it over manually faster than boost::asio
does.
Is there any way to improve bandwidth performance?
Upvotes: 2
Views: 573
Reputation: 393134
Your time likely is wasted in the serialization to/from text.
Dropping in binary archive does increase the speed from 80Mbit/s to 872MBit/s for me:
Client start to send message
Client send message
Client shutdown
Received: Hello World!
3
The total time in seconds is reduced to 3s, which happens to be the initial sleep :)
Proof Of Concept 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; }
One thing is "lost" here, that wasn't really supported with the old version of the code, either: receiving multiple archives directly head to head.
You might want to device some kind of framing protocol. See e.g.
I've done a number of "overhead of Boost Serialization" posts on here:
Upvotes: 2