diff --git a/example/circular_buffer_bound_example.cpp b/example/circular_buffer_bound_example.cpp index f55daa3d..731aadb4 100644 --- a/example/circular_buffer_bound_example.cpp +++ b/example/circular_buffer_bound_example.cpp @@ -11,6 +11,7 @@ This example shows how the `circular_buffer` can be utilized as an underlying container of the bounded buffer. */ +#include #include #include #include diff --git a/example/circular_buffer_serialization.cpp b/example/circular_buffer_serialization.cpp new file mode 100644 index 00000000..0552d89e --- /dev/null +++ b/example/circular_buffer_serialization.cpp @@ -0,0 +1,172 @@ +// Distributed under the Boost Software License, Version 1.0. +// (See the accompanying file LICENSE_1_0.txt +// or a copy at .) + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +int main(int argc, char** argv) +{ + int buffer_size = 100000; + + if (argc == 2) { + buffer_size = std::atoi(argv[1]); + if (buffer_size <= 0) { + std::cout << "invalid buffer size" << std::endl; + return 1; + } + } + + // Seed with a real random value, if available + std::random_device r; + + // Choose a randomly whether to push or pop + std::default_random_engine e(r()); + std::uniform_int_distribution uniform_dist(1, 10); + + { + boost::circular_buffer cb1(buffer_size); + for (auto i = 0; i < buffer_size; ++i) { + auto draw = uniform_dist(e); + if (draw < 8 || cb1.empty()) + cb1.push_back(i); + else + cb1.pop_front(); + } + + { + // in-memory storage text + std::stringstream ss; + + boost::archive::text_oarchive oa(ss); + std::chrono::steady_clock::time_point begin = std::chrono::steady_clock::now(); + boost::serialization::serialize(oa, cb1, 0); + std::chrono::steady_clock::time_point end = std::chrono::steady_clock::now(); + std::cout << "in-memory serialization for buffer: " << buffer_size << " took: " << + std::chrono::duration_cast (end - begin).count() << " usec" << std::endl; + + boost::circular_buffer cb2(0); + boost::archive::text_iarchive ia(ss); + begin = std::chrono::steady_clock::now(); + boost::serialization::serialize(ia, cb2, 0); + end = std::chrono::steady_clock::now(); + std::cout << "in-memory deserialization for buffer: " << buffer_size << " took: " << + std::chrono::duration_cast (end - begin).count() << " usec" << std::endl; + + if (cb1 != cb2) { + std::cout << "circular buffer did not recover correctly" << std::endl; + } + } + + { + // in-memory storage binary + std::stringstream ss; + + boost::archive::binary_oarchive oa(ss); + std::chrono::steady_clock::time_point begin = std::chrono::steady_clock::now(); + boost::serialization::serialize(oa, cb1, 0); + std::chrono::steady_clock::time_point end = std::chrono::steady_clock::now(); + std::cout << "in-memory binary serialization for buffer: " << buffer_size << " took: " << + std::chrono::duration_cast (end - begin).count() << " usec" << std::endl; + + boost::circular_buffer cb2(0); + boost::archive::binary_iarchive ia(ss); + begin = std::chrono::steady_clock::now(); + boost::serialization::serialize(ia, cb2, 0); + end = std::chrono::steady_clock::now(); + std::cout << "in-memory binary deserialization for buffer: " << buffer_size << " took: " << + std::chrono::duration_cast (end - begin).count() << " usec" << std::endl; + + if (cb1 != cb2) { + std::cout << "circular buffer did not recover correctly" << std::endl; + } + } + + { + const std::string filename = "cb.tmp"; + // file storage text + { + std::ofstream ofs(filename); + + boost::archive::text_oarchive oa(ofs); + std::chrono::steady_clock::time_point begin = std::chrono::steady_clock::now(); + boost::serialization::serialize(oa, cb1, 0); + std::chrono::steady_clock::time_point end = std::chrono::steady_clock::now(); + std::cout << "file serialization for buffer: " << buffer_size << " took: " << + std::chrono::duration_cast (end - begin).count() << " usec" << std::endl; + } + { + std::ifstream ifs(filename); + + boost::circular_buffer cb2(0); + boost::archive::text_iarchive ia(ifs); + std::chrono::steady_clock::time_point begin = std::chrono::steady_clock::now(); + boost::serialization::serialize(ia, cb2, 0); + std::chrono::steady_clock::time_point end = std::chrono::steady_clock::now(); + std::cout << "file deserialization for buffer: " << buffer_size << " took: " << + std::chrono::duration_cast (end - begin).count() << " usec" << std::endl; + + if (cb1 != cb2) { + std::cout << "circular buffer did not recover correctly" << std::endl; + } + } + std::remove(filename.c_str()); + } + } + + { + // space optimized + boost::circular_buffer_space_optimized cb1(buffer_size); + for (auto i = 0; i < buffer_size; ++i) { + auto draw = uniform_dist(e); + if (draw < 8 || cb1.empty()) { + cb1.push_back(i); + } else { + cb1.pop_front(); + } + } + + { + const std::string filename = "space_opt_cb.tmp"; + { + std::ofstream ofs(filename); + + boost::archive::text_oarchive oa(ofs); + std::chrono::steady_clock::time_point begin = std::chrono::steady_clock::now(); + boost::serialization::serialize(oa, cb1, 0); + std::chrono::steady_clock::time_point end = std::chrono::steady_clock::now(); + std::cout << "in-memory serialization for space optimized buffer: " << buffer_size << " took: " << + std::chrono::duration_cast (end - begin).count() << " usec" << std::endl; + } + + { + std::ifstream ifs(filename); + + boost::circular_buffer_space_optimized cb2(0); + boost::archive::text_iarchive ia(ifs); + std::chrono::steady_clock::time_point begin = std::chrono::steady_clock::now(); + boost::serialization::serialize(ia, cb2, 0); + std::chrono::steady_clock::time_point end = std::chrono::steady_clock::now(); + std::cout << "in-memory deserialization for space optimized buffer: " << buffer_size << " took: " << + std::chrono::duration_cast (end - begin).count() << " usec" << std::endl; + + if (cb1 != cb2) { + std::cout << "space optimized circular buffer did not recover correctly" << std::endl; + } + } + std::remove(filename.c_str()); + } + } + + return 0; +} + diff --git a/example/jamfile.v2 b/example/jamfile.v2 index 36db161b..607812cf 100644 --- a/example/jamfile.v2 +++ b/example/jamfile.v2 @@ -12,6 +12,7 @@ project : requirements /boost/system//boost_system /boost/thread//boost_thread + /boost//serialization #BOOST_ALL_NO_LIB=1 multi @@ -39,4 +40,5 @@ run bounded_buffer_comparison.cpp ; run circular_buffer_iter_example.cpp ; run circular_buffer_sum_example.cpp ; run circular_buffer_bound_example.cpp ../../thread/build//boost_thread ../../timer/build//boost_timer ; +run circular_buffer_serialization.cpp ; diff --git a/include/boost/circular_buffer.hpp b/include/boost/circular_buffer.hpp index f0530805..48bbf4a9 100644 --- a/include/boost/circular_buffer.hpp +++ b/include/boost/circular_buffer.hpp @@ -54,6 +54,9 @@ #include #include #include +#if !defined(BOOST_CIRCULAR_BUFFER_DISABLE_SERIALIZATION) +#include +#endif #undef BOOST_CB_ASSERT_TEMPLATED_ITERATOR_CONSTRUCTORS #undef BOOST_CB_IS_CONVERTIBLE diff --git a/include/boost/circular_buffer/serialization.hpp b/include/boost/circular_buffer/serialization.hpp new file mode 100644 index 00000000..170fc647 --- /dev/null +++ b/include/boost/circular_buffer/serialization.hpp @@ -0,0 +1,167 @@ +// Implementation of circular_buffer serialization + +// Use, modification, and distribution is subject to the Boost Software +// License, Version 1.0. (See accompanying file LICENSE_1_0.txt or copy at +// http://www.boost.org/LICENSE_1_0.txt) + +#include +#include +#include +#include +#include + +namespace boost { namespace serialization { +template +void save(Archive& ar, const circular_buffer& b, const unsigned int /* version */) +{ + ar << b.capacity(); + ar << b.size(); + const typename circular_buffer::const_iterator it_end = b.end(); + typename circular_buffer::const_iterator it = b.begin(); + for (; it != it_end; ++it) { + ar << *it; + } +} + +template +void save(archive::binary_oarchive& ar, const circular_buffer& b, const unsigned int /* version */) +{ + ar << b.capacity(); + const typename circular_buffer::const_array_range& one = b.array_one(); + const typename circular_buffer::const_array_range& two = b.array_two(); + ar << one.second; + ar << two.second; + if (one.second) { + ar.save_binary(one.first, one.second*sizeof(T)); + } + if (two.second) { + ar.save_binary(two.first, two.second*sizeof(T)); + } +} + +template +void load(Archive& ar, circular_buffer& b, const unsigned int /* version */) +{ + b.clear(); + typename circular_buffer::capacity_type capacity; + ar >> capacity; + b.set_capacity(capacity); + typename circular_buffer::size_type size; + ar >> size; + while (size > 0) { + T e; + ar >> e; + b.push_back(e); + --size; + } +} + +template +void load(archive::binary_iarchive& ar, circular_buffer& b, const unsigned int /* version */) +{ + b.clear(); + typename circular_buffer::capacity_type capacity; + ar >> capacity; + b.set_capacity(capacity); + typename circular_buffer::size_type size1; + typename circular_buffer::size_type size2; + ar >> size1; + ar >> size2; + const scoped_array::value_type> buff(new T[size1+size2]); + if (size1) { + ar.load_binary(buff.get(), size1*sizeof(T)); + } + if (size2) { + ar.load_binary(&buff[size1], size2*sizeof(T)); + } + b.insert(b.begin(), buff.get(), buff.get()+size1+size2); +} + +template +inline void serialize(Archive & ar, circular_buffer& b, const unsigned int version) +{ + split_free(ar, b, version); +} + +template +void save(Archive& ar, const circular_buffer_space_optimized& b, const unsigned int /* version */) +{ + ar << b.capacity().capacity(); + ar << b.capacity().min_capacity(); + ar << b.size(); + const typename circular_buffer_space_optimized::const_iterator it_end = b.end(); + typename circular_buffer_space_optimized::const_iterator it = b.begin(); + for (; it != it_end; ++it) { + ar << *it; + } +} + +template +void save(archive::binary_oarchive& ar, const circular_buffer_space_optimized& b, const unsigned int /* version */) +{ + ar << b.capacity().capacity(); + ar << b.capacity().min_capacity(); + const typename circular_buffer_space_optimized::const_array_range one = b.array_one(); + const typename circular_buffer_space_optimized::const_array_range two = b.array_two(); + ar << one.second; + ar << two.second; + if (one.second) { + ar.save_binary(one.first, one.second*sizeof(T)); + } + if (two.second) { + ar.save_binary(two.first, two.second*sizeof(T)); + } +} + +template +void load(Archive& ar, circular_buffer_space_optimized& b, const unsigned int /* version */) +{ + b.clear(); + typename circular_buffer_space_optimized::size_type capacity; + typename circular_buffer_space_optimized::size_type min_capacity; + ar >> capacity; + ar >> min_capacity; + const typename circular_buffer_space_optimized::capacity_type capacity_control(capacity, min_capacity); + b.set_capacity(capacity_control); + typename circular_buffer::size_type size; + ar >> size; + while (size > 0) { + T e; + ar >> e; + b.push_back(e); + --size; + } +} + +template +void load(archive::binary_iarchive& ar, circular_buffer_space_optimized& b, const unsigned int /* version */) +{ + b.clear(); + typename circular_buffer_space_optimized::size_type capacity; + typename circular_buffer_space_optimized::size_type min_capacity; + ar >> capacity; + ar >> min_capacity; + const typename circular_buffer_space_optimized::capacity_type capacity_control(capacity, min_capacity); + b.set_capacity(capacity_control); + typename circular_buffer::size_type size1; + typename circular_buffer::size_type size2; + ar >> size1; + ar >> size2; + const scoped_array::value_type> buff(new T[size1+size2]); + if (size1) { + ar.load_binary(buff.get(), size1*sizeof(T)); + } + if (size2) { + ar.load_binary(&buff[size1], size2*sizeof(T)); + } + b.insert(b.begin(), buff.get(), buff.get()+size1+size2); +} + +template +inline void serialize(Archive & ar, circular_buffer_space_optimized& b, const unsigned int version) +{ + split_free(ar, b, version); +} + +} } // end namespace boost::serialization + diff --git a/test/Jamfile.v2 b/test/Jamfile.v2 index 7e1ae584..f8ef194c 100644 --- a/test/Jamfile.v2 +++ b/test/Jamfile.v2 @@ -13,6 +13,9 @@ import testing ; project : requirements + /boost/test//boost_unit_test_framework + /boost/serialization + static msvc:all msvc:on msvc:_SCL_SECURE_NO_WARNINGS @@ -29,4 +32,5 @@ test-suite "circular_buffer" [ run soft_iterator_invalidation.cpp : : : single : ] [ run constant_erase_test.cpp : : : single : ] [ compile bounded_buffer_comparison.cpp : multi : ] + [ run serialization_test.cpp : : : single : ] ; diff --git a/test/serialization_test.cpp b/test/serialization_test.cpp new file mode 100644 index 00000000..d8960108 --- /dev/null +++ b/test/serialization_test.cpp @@ -0,0 +1,106 @@ +// Test of the circular buffer container serialization + +// Use, modification, and distribution is subject to the Boost Software +// License, Version 1.0. (See accompanying file LICENSE_1_0.txt or copy at +// http://www.boost.org/LICENSE_1_0.txt) + +#include +#include +#include +#include +#include +#include +#include +#include + +using namespace boost::unit_test; + +const int buffer_size = 100000; + +template +void basic_test() { + buffer_type cb1(buffer_size); + for (int i = 0; i < buffer_size; ++i) { + cb1.push_back(i); + } + + { + // in-memory storage + std::stringstream ss; + + oarchive_type oa(ss); + boost::serialization::serialize(oa, cb1, 0); + + buffer_type cb2(0); + iarchive_type ia(ss); + boost::serialization::serialize(ia, cb2, 0); + BOOST_CHECK(cb1 == cb2); + } +} + +template +void fragmented_buffer_test() { + + buffer_type cb1(buffer_size); + for (int i = 0; i < buffer_size/4; ++i) + cb1.push_back(i+1); + for (int i = 0; i < buffer_size/4; ++i) + cb1.push_front((i+1)*100); + + // making sure we are testing a fragmented buffer + BOOST_CHECK(!cb1.is_linearized()); + + std::stringstream ss; + + oarchive_type oa(ss); + boost::serialization::serialize(oa, cb1, 0); + + buffer_type cb2(0); + iarchive_type ia(ss); + boost::serialization::serialize(ia, cb2, 0); + + BOOST_CHECK(cb1 == cb2); +} + +void basic_test_text() { + basic_test, boost::archive::text_oarchive, boost::archive::text_iarchive>(); +} +void basic_test_optimized_text() { + basic_test, boost::archive::text_oarchive, boost::archive::text_iarchive>(); +} +void fragmented_test_text() { + fragmented_buffer_test, boost::archive::text_oarchive, boost::archive::text_iarchive>(); +} +void fragmented_test_optimized_text() { + fragmented_buffer_test, boost::archive::text_oarchive, boost::archive::text_iarchive>(); +} +void basic_test_binary() { + basic_test, boost::archive::binary_oarchive, boost::archive::binary_iarchive>(); +} +void basic_test_optimized_binary() { + basic_test, boost::archive::binary_oarchive, boost::archive::binary_iarchive>(); +} +void fragmented_test_binary() { + fragmented_buffer_test, boost::archive::binary_oarchive, boost::archive::binary_iarchive>(); +} +void fragmented_test_optimized_binary() { + fragmented_buffer_test, boost::archive::binary_oarchive, boost::archive::binary_iarchive>(); +} + +// test main +test_suite* init_unit_test_suite(int /*argc*/, char* /*argv*/[]) { + + test_suite* tests = BOOST_TEST_SUITE("Unit tests for circular_buffer serialization."); + + tests->add(BOOST_TEST_CASE(&basic_test_text)); + tests->add(BOOST_TEST_CASE(&basic_test_optimized_text)); + tests->add(BOOST_TEST_CASE(&fragmented_test_text)); + tests->add(BOOST_TEST_CASE(&fragmented_test_optimized_text)); + tests->add(BOOST_TEST_CASE(&basic_test_binary)); + tests->add(BOOST_TEST_CASE(&basic_test_optimized_binary)); + tests->add(BOOST_TEST_CASE(&fragmented_test_binary)); + tests->add(BOOST_TEST_CASE(&fragmented_test_optimized_binary)); + + return tests; +} +