Boost logo

Boost Users :

Subject: [Boost-users] [serialization] Runtime overhead of serialization archives
From: georg_at_[hidden]
Date: 2016-09-21 04:14:20


Hi all,
currently i try to measure the runtime overhead of serialization vs. plain
c-style serialization. The goal is to send it from one process to an other
process on the same machine. As my statemachines are written with
boost::msm, i want to deliver types.

For the IPC i used interprocess::message_queue. I wrote a template
function for the test and added a trait to plug in the test functions.

I tested on the same machine, once with Linux Debian stretch x64 and Win
10 x64 MSVC 2015.

What really astonish me is the fact, that the measured times using
boost::serilization is so high compared to "c-style: id + data" method. In
the c-style method i used a FNV hash of the type name as the ID.

All tests were done on a Intel Core i7 2670QM CPU. All results in sec.

I sent/received 100000 objects over a message_queue.

Boost 1.61.0 Linux x86_64 / gcc 6.1.1 Win 10 x64/ MSVC 2015
Boost XML Send 2.220753 8.255834
Boost XML Receive 3.208353 10.14462
Boost Text Send 2.024946 8.578654
Boost Text Receive 3.207359 10.704126
Boost BinarySend 2.018026 8.363865
Boost Binary Receive 3.17984 11.201501
Cstyle Send 0.13566 0.056814
Cstyle Receive 0.087906 0.058706
Char Send 0.071683 0.013965
Char Receive 0.062119 0.012631

To measure the real overhead of the message passing, i made a test and
just sent 100000 plain chars over the "wire".

There are two strange things:
a) The serilization with boost seems to be about 16 times slower than the
plain c-style method, the receive seems to be about 30 times slower. I
think i do something wrong..... he tests were compiled on release mode.

b) On the same hardware, the Windows implementation is so much slower than
the Linux one... about factor 3. But at the c-style method, it turns
around.... Linux is slower than Windows.

Has anyone an idea whats the issue here?

I added the code at the end of this text

Best regards
Georg

// -----------------------------------------------------
// Code
// -----------------------------------------------------
#define BOOST_TEST_MODULE first_tests
#include <boost/test/unit_test.hpp>

#include <boost/interprocess/ipc/message_queue.hpp> // send all data
through this
#include <boost/timer/timer.hpp> // measure the used time
#include <iostream> // for output

// STL Archive + Stuff
#include <boost/serialization/base_object.hpp>
#include <boost/serialization/export.hpp>
#include <boost/serialization/shared_ptr.hpp>
#include <boost/serialization/unique_ptr.hpp>

// include headers that implement a archive in xml format
#include <boost/archive/archive_exception.hpp>

#include <boost/archive/xml_iarchive.hpp>
#include <boost/archive/xml_oarchive.hpp>

#include <boost/archive/text_iarchive.hpp>
#include <boost/archive/text_oarchive.hpp>

#include <boost/archive/binary_iarchive.hpp>
#include <boost/archive/binary_oarchive.hpp>

#include <boost/iostreams/device/array.hpp>
#include <boost/iostreams/device/back_inserter.hpp>
#include <boost/iostreams/stream.hpp>

#include <memory>
#include <stdint.h>
#include <typeinfo>
#include <vector>

using namespace boost::interprocess;

static const int test_count = 100000; // send this many structs

// the test structure
struct ev_test
{
        long a = 1;
        unsigned long b = 2;
};

//
----------------------------------------------------------------------------
// a packet on the wire
//
----------------------------------------------------------------------------
using packet = std::vector<char>;

//-----------------------------------------------------------------------------
// Type carrier and its support
//
----------------------------------------------------------------------------
namespace
{
        class carrier_visitor_base;

        class carrier_base // the base in the queue
        {
        public:
                using ptr = std::unique_ptr<carrier_base>;

                virtual ~carrier_base() {}

                virtual void accept(carrier_visitor_base *p_visitor) = 0;

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

        template <typename T>
        class carrier;

        class carrier_visitor_base
        {
        public:
                virtual ~carrier_visitor_base() {}

                virtual void handle(carrier<ev_test> *p_evt) = 0;
                virtual void handle(carrier<char> *p_evt) = 0;
        };

        template <typename T>
        class carrier : public carrier_base // the specific carrier
        {
        public:
                explicit carrier() : m_data() {}

                explicit carrier(const T &data) : m_data(data) {}
                virtual void accept(carrier_visitor_base *p_visitor) override
                {
                        p_visitor->handle(this);
                }

                T &data() { return m_data; }
        private:
                T m_data;
        };
} // ns anon

//
----------------------------------------------------------------------------
// the traits for the chartest
//
----------------------------------------------------------------------------
struct char_test
{
        static const char *name() { return "char_test 'c': "; }
        static size_t msg_size() { return sizeof(char); }

        static char get_data() { return 'c'; }

        template <typename T>
        static packet to_wire(T data)
        {
                packet ret;
                ret.push_back(get_data());
                return ret;
        }

        static carrier_base::ptr from_wire(const packet &data)
        {
                auto p_data = new carrier<char>();
                p_data->data() = data[0];
                return std::unique_ptr<carrier<char>>(p_data);
        }
};

//
----------------------------------------------------------------------------
// the traits for the boost xml serialization test
//
----------------------------------------------------------------------------
//
----------------------------------------------------------------------------
// external serialization function
//
----------------------------------------------------------------------------
namespace boost
{
        namespace serialization
        {

                // serialization function for ev_test
                template <class Archive>
                inline void serialize(Archive &ar, ev_test &t, const unsigned int version)
                {
                        ar &BOOST_SERIALIZATION_NVP(t.a);
                        ar &BOOST_SERIALIZATION_NVP(t.b);
                }

                // serialization function for carrier<T>
                template <class Archive, typename T>
                void serialize(Archive &ar, carrier<T> &t, const unsigned int version)
                {
                        ar &boost::serialization::make_nvp(
                                "carrier_base", boost::serialization::base_object<carrier_base>(t));
                        // BOOST_SERIALIZATION_BASE_OBJECT_NVP(a);

                        auto &data = t.data();
                        ar &BOOST_SERIALIZATION_NVP(data);
                }
        }
}

// we must export all carrier
BOOST_SERIALIZATION_SHARED_PTR(carrier<ev_test>)
BOOST_CLASS_EXPORT(carrier<ev_test>)

struct boost_xml_trait
{
        static const char *name() { return "boost_xml_test: ev_test: "; }

        typedef boost::archive::xml_oarchive oarchive;
        typedef boost::archive::xml_iarchive iarchive;
};

struct boost_text_trait
{
        static const char *name() { return "boost_text_test: ev_test: "; }

        typedef boost::archive::xml_oarchive oarchive;
        typedef boost::archive::xml_iarchive iarchive;
};

struct boost_binary_trait
{
        static const char *name() { return "boost_binary_test: ev_test: "; }

        typedef boost::archive::xml_oarchive oarchive;
        typedef boost::archive::xml_iarchive iarchive;
};

template <typename archive_trait>
struct boost_test
{
        static const char *name() { return archive_trait::name(); }
        static size_t msg_size() { return 600; }

        // throws boost::archive::archive_exception
        template <typename T>
        static packet to_wire(T data)
        {
                using namespace boost::iostreams;

                using T1 = typename std::remove_cv<T>::type;
                using BT = typename std::remove_reference<T1>::type;
                carrier_base::ptr p_carrier = std::make_unique<carrier<BT>>(data);

                packet p;
                {
                        back_insert_device<packet> sink{ p };
                        stream<back_insert_device<packet>> os{ sink };

                        typename archive_trait::oarchive oa(os);
                        oa << BOOST_SERIALIZATION_NVP(p_carrier);
                }
                return p;
        }

        // throws boost::archive::archive_exception
        static carrier_base::ptr from_wire(const packet &data)
        {
                using namespace boost::iostreams;
                carrier_base::ptr p_carrier;

                boost::iostreams::array_source source{ data.data(), data.size() };
                stream<array_source> is{ source };
                typename archive_trait::iarchive ia(is); // this takes the most time
                ia >> BOOST_SERIALIZATION_NVP(p_carrier);

                return p_carrier;
        }
};

//
----------------------------------------------------------------------------
// the traits for the cstyle serialization test
//
----------------------------------------------------------------------------
//
----------------------------------------------------------------------------
// cstyle serialization with fnv type identifier
//
----------------------------------------------------------------------------
struct cstyle_test
{
        static const char *name() { return "cstyle_test: "; }

        static size_t msg_size() { return 1000; }

        // Fowler–Noll–Vo hash function
        //
https://en.wikipedia.org/wiki/Fowler%E2%80%93Noll%E2%80%93Vo_hash_function
        struct fnv
        {
                const static uint64_t base = 0xcbf29ce484222325;
                const static uint64_t prime = 0x00000100000001b3;

                const static uint64_t next(uint64_t current, size_t char_code)
                {
                        return (current ^ char_code) * prime;
                }

                const static uint64_t value(const char *cptr,
                        uint64_t interim = base)
                {
                        return (*cptr == '\0')
                                ? interim
                                : value(cptr + 1,
                                next(interim, static_cast<size_t>(*cptr)));
                }
        };

        template <typename T>
        static packet to_wire(T data)
        {
                packet buffer(msg_size()); // to be fair, send the same amount of bytes
                const uint64_t id = fnv::value(typeid(T).name());
                size_t pos = 0;

                assert(msg_size() >= sizeof(uint64_t) + sizeof(T));
                // buffer.resize(buffer.size() + sizeof(uint64_t) + sizeof(T));

                std::memcpy(buffer.data() + pos, &id, sizeof(uint64_t));
                pos += sizeof(uint64_t);

                std::memcpy(buffer.data() + pos, &data, sizeof(T));
                pos += sizeof(T);

                return buffer;
        }

        static carrier_base::ptr from_wire(const packet &data)
        {
                // get the id
                size_t pos = 0;
                uint64_t id;
                std::memcpy(&id, data.data() + pos, sizeof(uint64_t));
                pos += sizeof(uint64_t);

                // create the specific pointer
                if (id == fnv::value(typeid(ev_test).name()))
                {
                        assert(data.size() >= sizeof(uint64_t) + sizeof(ev_test));

                        auto p_evt = new carrier<ev_test>();
                        std::memcpy(&p_evt->data(), data.data() + pos, sizeof(ev_test));
                        return carrier_base::ptr(p_evt);
                }
                throw std::runtime_error("deserialize error");
        }
};

template <typename trait>
void do_test()
{
        class runtime : public carrier_visitor_base
        {
        public:
                virtual void handle(carrier<ev_test> *p_evt) override
                {
                        i += p_evt->data().a;
                }
                virtual void handle(carrier<char> *p_evt) override
                {
                        i += p_evt->data();
                }

                int i = 0;
        };

        try
        {
                std::cout << "Sending " << trait::name() << test_count << std::endl;

                // Erase previous message queue
                message_queue::remove("message_queue");

                // Create a message_queue.
                message_queue mq(create_only // only create
                        ,
                        "message_queue" // name
                        ,
                        test_count // max message number
                        ,
                        trait::msg_size() // max message size
                        );

                // send ev_tests
                boost::timer::auto_cpu_timer t;
                for (int i = 0; i < test_count; ++i)
                {
                        auto buffer = trait::to_wire(ev_test());

                        mq.send(buffer.data(), buffer.size(), 0);
                }
        }
        catch (const interprocess_exception &ex)
        {
                message_queue::remove("message_queue");
                std::cout << ex.what() << std::endl;
                BOOST_CHECK(true == false);
        }

        runtime rt;
        try
        {
                std::cout << "Receiving " << trait::name() << test_count << std::endl;

                // Open a message queue.
                message_queue mq(open_only // only create
                        ,
                        "message_queue" // name
                        );
                message_queue::size_type recvd_size;

                packet buffer(trait::msg_size());
                unsigned int priority;

                boost::timer::auto_cpu_timer t;
                for (int i = 0; i < test_count; ++i)
                {
                        // receive the raw data
                        mq.receive(buffer.data(), buffer.size(), recvd_size, priority);

                        // deserialize
                        auto p_carrier = trait::from_wire(buffer);

                        // handle it to the protocol
                        p_carrier->accept(&rt);
                }
        }
        catch (const interprocess_exception &ex)
        {
                message_queue::remove("message_queue");
                std::cout << ex.what() << std::endl;
                BOOST_CHECK(true == false);
        }
        message_queue::remove("message_queue");

        std::cout << "RT Counter: " << rt.i << std::endl << std::endl;
}

BOOST_AUTO_TEST_CASE(carrier_boost_xml_ev_test)
{
        do_test<boost_test<boost_xml_trait>>();
}

BOOST_AUTO_TEST_CASE(carrier_boost_text_ev_test)
{
        do_test<boost_test<boost_text_trait>>();
}

BOOST_AUTO_TEST_CASE(carrier_boost_binary_ev_test)
{
        do_test<boost_test<boost_binary_trait>>();
}

BOOST_AUTO_TEST_CASE(carrier_cstyle_ev_test) { do_test<cstyle_test>(); }

BOOST_AUTO_TEST_CASE(carrier_char_test) { do_test<char_test>(); }


Boost-users list run by williamkempf at hotmail.com, kalb at libertysoft.com, bjorn.karlsson at readsoft.com, gregod at cs.rpi.edu, wekempf at cox.net