Boost logo

Boost Users :

Subject: Re: [Boost-users] [serialization] Runtime overhead of serialization archives
From: Georg Gast (georg_at_[hidden])
Date: 2016-09-23 13:41:17


Am 22.09.2016 um 22:38 schrieb Chris Glover:
> For what it's worth, in tests I've done in the past, binary
> serialization using boost.serialization and other similar systems was
> not this big of a difference compared to memcpy. I was seeing maybe a 5x
> to 10x difference compared to memcpy (yours is 50x).
>
> Of course, this depends on a lot of factors, like how much data this is
> because it would determine if you are memory bound or not, but I am
> wondering if your cstyle tests are actually being completely optimized
> away. Have you examined the disassembly? If you find the code is being
> optimized away, Google Benchmark has a handy "benchmark::DoNotOptimize"
> function to help keep the optimizer from throwing away the side effects
> of a particular address.

Thanks for that hint. I changed the test and set up a range for the data
size. As the size exceeded about 2 Mb, the cstyle thing got half as fast
as the boost::binary_archive. That surprised me and i decided to not go
that path (cstyle), because i really like to use the library.

As i setup the ranges for the sizes, i got a lot of results. In fact one
thing is notable, the processed bytes/sec.

As the data size grows higher than 512 bytes, the processing speed
settles at the max rate (for textual archives). It seems each kind of
archive has its own limit. The xml archive, as its the most verbose, has
the least speed. The binary_archive seems to "cheat" on this test... 7
GB/s ... I guess it just lays totally in the cache.

The test were done with gcc 6.1.1/boost 1.61.0 and -O3 optimization.

For documentation issues, i add here my results on linux and the current
code.

  Georg

Benchmark Time(ns) CPU(ns) Iterations Bandwidth
-------------------------------------------------------------------
to_wire_xml/8 15797 15840 31818 493.211kB/s
to_wire_xml/64 37456 37385 18617 1.6326MB/s
to_wire_xml/512 211649 211188 3182 2.31207MB/s
to_wire_xml/4k 1639611 1639344 427 2.38281MB/s
to_wire_xml/32k 13641742 13647059 51 2.28987MB/s
to_wire_xml/256k 106978476 107333333 6 2.32919MB/s
to_wire_xml/2M 870869606 872000000 1 2.29358MB/s
to_wire_xml/4M 1819503270 1816000000 1 2.20264MB/s
from_wire_xml/8 31584 31600 22152 247.232kB/s
from_wire_xml/64 56806 56640 12500 1103.46kB/s
from_wire_xml/512 240413 239021 2778 2.04284MB/s
from_wire_xml/4k 1742682 1739558 407 2.24554MB/s
from_wire_xml/32k 14104072 14122449 49 2.21279MB/s
from_wire_xml/256k 113079335 113142857 7 2.2096MB/s
from_wire_xml/2M 846656504 844000000 1 2.36967MB/s
from_wire_xml/8M 3387609285 3388000000 1 2.36128MB/s
to_wire_text/8 6204 6181 109375 1.23442MB/s
to_wire_text/64 9197 9200 76087 6.63426MB/s
to_wire_text/512 31154 31095 23026 15.7027MB/s
to_wire_text/4k 201879 200892 3365 19.4446MB/s
to_wire_text/32k 1624883 1620609 427 19.2829MB/s
to_wire_text/256k 12647559 12654545 55 19.7557MB/s
to_wire_text/2M 100406115 100000000 7 20MB/s
to_wire_text/4M 216889302 216000000 3 18.5185MB/s
from_wire_text/8 6283 6256 102941 1.21953MB/s
from_wire_text/64 9104 9095 76087 6.71096MB/s
from_wire_text/512 33779 33810 20349 14.4419MB/s
from_wire_text/4k 224963 225219 2966 17.3442MB/s
from_wire_text/32k 1759826 1757895 380 17.7769MB/s
from_wire_text/256k 14159723 14122449 49 17.7023MB/s
from_wire_text/2M 112441804 112666667 6 17.7515MB/s
from_wire_text/4M 224818542 225333333 3 17.7515MB/s
to_wire_binary/8 4257 4256 163551 1.79281MB/s
to_wire_binary/64 4405 4394 162037 13.8904MB/s
to_wire_binary/512 4324 4325 159091 112.909MB/s
to_wire_binary/4k 5180 5200 134615 751.2MB/s
to_wire_binary/32k 11714 11657 58333 2.61791GB/s
to_wire_binary/256k 74599 74693 9211 3.26857GB/s
to_wire_binary/2M 1160753 1159520 583 1.68443GB/s
to_wire_binary/4M 2583586 2578755 273 1.51478GB/s
from_wire_binary/8 3509 3500 201149 2.17989MB/s
from_wire_binary/64 3476 3480 201149 17.5388MB/s
from_wire_binary/512 3601 3598 192308 135.694MB/s
from_wire_binary/4k 3833 3840 182292 1017.25MB/s
from_wire_binary/32k 6697 6683 102941 4.56615GB/s
from_wire_binary/256k 33168 33201 21084 7.35352GB/s
from_wire_binary/2M 268648 268842 2574 7.26495GB/s
from_wire_binary/4M 820816 821128 833 4.75717GB/s

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

// include headers that implement a archives in xml/text/binary 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>

// IO stream for the to/from wire functions
#include <boost/iostreams/device/array.hpp>
#include <boost/iostreams/device/back_inserter.hpp>
#include <boost/iostreams/stream.hpp>

#include <memory>
#include <cstdint>
#include <vector>

#include <benchmark/benchmark.h>

// the step interval for the benchmarks
static const int range_mult = 4;
static const int range_max_step = 20;

// the test structure
struct ev_test
{
        ev_test(size_t s = 0)
        {
                m_data.resize(s);
                for (auto &c : m_data)
                        c = 1;
        }

        std::vector<uint8_t> m_data;
};

//-----------------------------------------------------------------------------
// Type carrier and its support
//----------------------------------------------------------------------------
namespace net
{
using packet = std::vector<char>; // a packet on the wire

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 <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;
        virtual void handle(carrier<int> *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 net

//----------------------------------------------------------------------------
// external serialization function
//----------------------------------------------------------------------------
BOOST_SERIALIZATION_SPLIT_FREE(ev_test)
namespace boost
{
namespace serialization
{
// serialization function for carrier_base
template <class Archive>
void serialize(Archive &ar, net::carrier_base &t, const unsigned int
version)
{
}

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

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

// serialization function for ev_test
template <class Archive>
inline void save(Archive &ar, const ev_test &t, const unsigned int version)
{
        size_t size = t.m_data.size();
        ar &BOOST_SERIALIZATION_NVP(size);
        ar &boost::serialization::make_nvp(
                "m_data",
                boost::serialization::make_array(t.m_data.data(), t.m_data.size()));
}
template <class Archive>
inline void load(Archive &ar, ev_test &t, const unsigned int version)
{
        size_t size = 0;
        ar &BOOST_SERIALIZATION_NVP(size);
        t.m_data.resize(size);

        ar &boost::serialization::make_nvp(
                "m_data",
                boost::serialization::make_array(t.m_data.data(), t.m_data.size()));
}
}
}

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

//----------------------------------------------------------------------------
// the traits for the boost serialization tests
//----------------------------------------------------------------------------
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::text_oarchive oarchive;
        typedef boost::archive::text_iarchive iarchive;
};

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

        typedef boost::archive::binary_oarchive oarchive;
        typedef boost::archive::binary_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 net::packet to_wire(const T &data)
        {
                using namespace boost::iostreams;

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

                net::packet p;
                p.reserve(msg_size());
                {
                        back_insert_device<net::packet> sink(p);
                        stream<back_insert_device<net::packet>> os{sink};

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

        // throws boost::archive::archive_exception
        static net::carrier_base::ptr from_wire(const net::packet &data)
        {
                using namespace boost::iostreams;
                array_source source{data.data(), data.size()};
                stream<array_source> is{source};

                net::carrier_base::ptr p_carrier;
                typename archive_trait::iarchive ia(is); // this takes the most time
                ia >> BOOST_SERIALIZATION_NVP(p_carrier);

                return p_carrier;
        }
};

//----------------------------------------------------------------------------
// XML
//----------------------------------------------------------------------------
static void to_wire_xml(benchmark::State &state)
{
        std::locale::global(std::locale("C"));
        ev_test data(state.range_x());

        while (state.KeepRunning())
        {
                boost_test<boost_xml_trait>::to_wire(data);
        }

        state.SetBytesProcessed(static_cast<int64_t>(state.iterations()) *
                                                        state.range_x());
}
BENCHMARK(to_wire_xml)->Range(8, range_mult << range_max_step);

static void from_wire_xml(benchmark::State &state)
{
        std::locale::global(std::locale("C"));
        auto buffer =
                boost_test<boost_xml_trait>::to_wire(ev_test(state.range_x()));

        while (state.KeepRunning())
        {
                boost_test<boost_xml_trait>::from_wire(buffer);
        }

        state.SetBytesProcessed(static_cast<int64_t>(state.iterations()) *
                                                        state.range_x());
}
BENCHMARK(from_wire_xml)->Range(8, 8 << range_max_step);

//----------------------------------------------------------------------------
// Text
//----------------------------------------------------------------------------
static void to_wire_text(benchmark::State &state)
{
        std::locale::global(std::locale("C"));
        ev_test data(state.range_x());

        while (state.KeepRunning())
        {
                boost_test<boost_text_trait>::to_wire(data);
        }

        state.SetBytesProcessed(static_cast<int64_t>(state.iterations()) *
                                                        state.range_x());
}
BENCHMARK(to_wire_text)->Range(8, range_mult << range_max_step);

static void from_wire_text(benchmark::State &state)
{
        std::locale::global(std::locale("C"));
        auto buffer =
                boost_test<boost_text_trait>::to_wire(ev_test(state.range_x()));

        while (state.KeepRunning())
        {
                boost_test<boost_text_trait>::from_wire(buffer);
        }

        state.SetBytesProcessed(static_cast<int64_t>(state.iterations()) *
                                                        state.range_x());
}
BENCHMARK(from_wire_text)->Range(8, range_mult << range_max_step);

//----------------------------------------------------------------------------
// Binary
//----------------------------------------------------------------------------
static void to_wire_binary(benchmark::State &state)
{
        std::locale::global(std::locale("C"));
        ev_test data(state.range_x());

        while (state.KeepRunning())
        {
                boost_test<boost_binary_trait>::to_wire(data);
        }

        state.SetBytesProcessed(static_cast<int64_t>(state.iterations()) *
                                                        state.range_x());
}
BENCHMARK(to_wire_binary)->Range(8, range_mult << range_max_step);

static void from_wire_binary(benchmark::State &state)
{
        std::locale::global(std::locale("C"));

        auto buffer =
                boost_test<boost_binary_trait>::to_wire(ev_test(state.range_x()));

        while (state.KeepRunning())
        {
                boost_test<boost_binary_trait>::from_wire(buffer);
        }

        state.SetBytesProcessed(static_cast<int64_t>(state.iterations()) *
                                                        state.range_x());
}
BENCHMARK(from_wire_binary)->Range(8, range_mult << range_max_step);

BENCHMARK_MAIN();

</code>

-- 
pgp key: 0x702C5BFC
Fingerprint: 267F DC06 7F96 3375 969A  9EE6 8E37 7CF4 702C 5BFC

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