diff --git a/src/utils/include/utils/serialization/memcpy_archive.hpp b/src/utils/include/utils/serialization/memcpy_archive.hpp
new file mode 100644
index 00000000000..9a98d8cd45f
--- /dev/null
+++ b/src/utils/include/utils/serialization/memcpy_archive.hpp
@@ -0,0 +1,231 @@
+/*
+ * Copyright (C) 2019 The ESPResSo project
+ *
+ * This file is part of ESPResSo.
+ *
+ * ESPResSo is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * ESPResSo is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ */
+#ifndef ESPRESSO_MEMCPY_ARCHIVE_HPP
+#define ESPRESSO_MEMCPY_ARCHIVE_HPP
+
+#include
+
+#include
+#include
+#include
+
+#include
+
+namespace Utils {
+/** @brief Type trait to indicate that a type is
+ * serializable with a static size, e.g. is
+ * suitable for memcpy serialization. Only
+ * specialize this to std::true_type if it is
+ * guarantueed that serializing this type always
+ * returns the same number of bytes, independent
+ * of object state.
+ *
+ * @tparam T type under consideration.
+ */
+template struct is_statically_serializable : std::false_type {};
+
+namespace detail {
+/* Use serialize function only if the type is opt-in but not
+ * trivially copyable, in which case memcpy is more efficient. */
+template
+using use_serialize =
+ std::integral_constant::value and
+ is_statically_serializable::value>;
+
+struct SizeArchive {
+ using is_saving = boost::mpl::true_;
+ using is_loading = boost::mpl::false_;
+
+ size_t size = {};
+
+ template
+ constexpr auto operator<<(T const &)
+ -> std::enable_if_t::value> {
+ size += sizeof(T);
+ }
+
+ template
+ constexpr auto operator<<(T &v)
+ -> std::enable_if_t::value> {
+ boost::serialization::serialize(*this, v, 0);
+ }
+
+ template constexpr auto operator<<(boost::optional &) {
+ operator<<(bool());
+ operator<<(T{});
+ }
+
+ template constexpr SizeArchive &operator&(T &t) {
+ operator<<(t);
+
+ return *this;
+ }
+};
+
+class BasicMemcpyArchive {
+ /** Buffer to write to */
+ Utils::Span buf;
+ /** Current position in the buffer */
+ char *insert;
+
+public:
+ explicit BasicMemcpyArchive(Utils::Span buf)
+ : buf(buf), insert(buf.data()) {}
+
+ size_t bytes_processed() const { return insert - buf.data(); }
+
+ template
+ auto operator>>(T &value)
+ -> std::enable_if_t::value> {
+ /* check that there is enough space left in the buffer */
+ assert((insert + sizeof(T)) <= buf.end());
+
+ std::memcpy(&value, insert, sizeof(T));
+ insert += sizeof(T);
+ }
+
+ template
+ auto operator<<(T const &value)
+ -> std::enable_if_t::value> {
+ /* check that there is enough space left in the buffer */
+ assert((insert + sizeof(T)) <= buf.end());
+ std::memcpy(insert, &value, sizeof(T));
+ insert += sizeof(T);
+ }
+
+ /**
+ * @brief Determine the static packing size of a type.
+ * @tparam T Type to consider.
+ * @return Packed size in bytes.
+ */
+ template static size_t packing_size() {
+ detail::SizeArchive sa{};
+ T t;
+ return (sa & t).size;
+ }
+};
+} // namespace detail
+
+/**
+ * @brief Archive that deserializes from a buffer via memcpy.
+ *
+ * Can only process types that have a static serialization size,
+ * e.g. that serialize to the same number of bytes independent of
+ * the state of the object. This can either be automatically detected
+ * for types that are trivially copyable, or by explicitly assuring
+ * this by specializing @c is_statically_serializable to std::true_type.
+ */
+class MemcpyIArchive : public detail::BasicMemcpyArchive {
+private:
+ using base_type = detail::BasicMemcpyArchive;
+
+public:
+ using is_loading = boost::mpl::true_;
+ using is_saving = boost::mpl::false_;
+
+ /**
+ * @param buf Buffer to read from.
+ */
+ explicit MemcpyIArchive(Utils::Span buf)
+ : detail::BasicMemcpyArchive(buf) {}
+
+ /**
+ * @brief Number of bytes read from the buffer.
+ * @return Number of bytes read.
+ */
+ size_t bytes_read() const { return bytes_processed(); }
+
+ /** @copydoc base_type::packing_size */
+ using base_type::packing_size;
+ using base_type::operator>>;
+
+ template
+ auto operator>>(T &value)
+ -> std::enable_if_t::value> {
+ boost::serialization::serialize(*this, value, 0);
+ }
+
+ template void operator>>(boost::optional &o) {
+ bool initialized{};
+ operator>>(initialized);
+ T val{};
+ operator>>(val);
+
+ if (initialized) {
+ o = val;
+ } else {
+ o = boost::none;
+ }
+ }
+
+ template MemcpyIArchive &operator&(T &value) {
+ operator>>(value);
+
+ return *this;
+ }
+};
+
+/**
+ * @brief Archive that serializes to a buffer via memcpy.
+ *
+ * @copydetails MemcpyIArchive
+ */
+class MemcpyOArchive : public detail::BasicMemcpyArchive {
+ using base_type = detail::BasicMemcpyArchive;
+
+public:
+ using is_loading = boost::mpl::false_;
+ using is_saving = boost::mpl::true_;
+
+ /**
+ * @param buf Buffer to write to.
+ */
+ explicit MemcpyOArchive(Utils::Span buf)
+ : detail::BasicMemcpyArchive(buf) {}
+
+ /**
+ * @brief Number of bytes written to the buffer.
+ * @return Number of bytes written.
+ */
+ size_t bytes_written() const { return bytes_processed(); }
+
+ /** @copydoc base_type::packing_size */
+ using base_type::packing_size;
+ using base_type::operator<<;
+
+ template
+ auto operator<<(T &value)
+ -> std::enable_if_t::value> {
+ boost::serialization::serialize(*this, value, 0);
+ }
+
+ template void operator<<(boost::optional &o) {
+ operator<<(o.is_initialized());
+ operator<<(o.value_or(T{}));
+ }
+
+ template MemcpyOArchive &operator&(T &value) {
+ operator<<(value);
+
+ return *this;
+ }
+};
+} // namespace Utils
+
+#endif // ESPRESSO_MEMCPY_ARCHIVE_HPP
diff --git a/src/utils/tests/CMakeLists.txt b/src/utils/tests/CMakeLists.txt
index 2fbe6cbd52e..d3c7380ce3e 100644
--- a/src/utils/tests/CMakeLists.txt
+++ b/src/utils/tests/CMakeLists.txt
@@ -40,6 +40,7 @@ unit_test(NAME quaternion_test SRC quaternion_test.cpp DEPENDS utils)
unit_test(NAME mask_test SRC mask_test.cpp DEPENDS utils)
unit_test(NAME type_traits_test SRC type_traits_test.cpp DEPENDS utils)
unit_test(NAME uniform_test SRC uniform_test.cpp DEPENDS utils)
+unit_test(NAME memcpy_archive_test SRC memcpy_archive_test.cpp DEPENDS utils)
unit_test(NAME gather_buffer_test SRC gather_buffer_test.cpp DEPENDS utils Boost::mpi MPI::MPI_CXX NUM_PROC 4)
unit_test(NAME scatter_buffer_test SRC scatter_buffer_test.cpp DEPENDS utils Boost::mpi MPI::MPI_CXX NUM_PROC 4)
diff --git a/src/utils/tests/memcpy_archive_test.cpp b/src/utils/tests/memcpy_archive_test.cpp
new file mode 100644
index 00000000000..4a0441e0d9d
--- /dev/null
+++ b/src/utils/tests/memcpy_archive_test.cpp
@@ -0,0 +1,109 @@
+/*
+ * Copyright (C) 2019 The ESPResSo project
+ *
+ * This file is part of ESPResSo.
+ *
+ * ESPResSo is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * ESPResSo is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ */
+
+#define BOOST_TEST_MODULE memcpy archive test
+#define BOOST_TEST_DYN_LINK
+#include
+
+#include "utils/serialization/memcpy_archive.hpp"
+
+#include
+#include
+
+#include
+
+struct NonTrivial {
+ boost::optional ov;
+
+ template void serialize(Archive &ar, long int) { ar &ov; }
+};
+
+using OpVec = boost::optional;
+
+namespace Utils {
+template <> struct is_statically_serializable : std::true_type {};
+} // namespace Utils
+
+BOOST_AUTO_TEST_CASE(packing_size_test) {
+ BOOST_CHECK_EQUAL(Utils::MemcpyIArchive::packing_size(), sizeof(int));
+
+ {
+ BOOST_CHECK_EQUAL(Utils::MemcpyIArchive::packing_size(),
+ Utils::MemcpyIArchive::packing_size());
+ }
+}
+
+BOOST_AUTO_TEST_CASE(optional_packing) {
+ std::vector buf(2 * Utils::MemcpyOArchive::packing_size());
+
+ const OpVec active = Utils::Vector3d{1., 2., 3.};
+ const OpVec inactive = boost::none;
+ {
+ auto oa = Utils::MemcpyOArchive{Utils::make_span(buf)};
+ auto in1 = active;
+ auto in2 = inactive;
+ oa << in1;
+ oa << in2;
+
+ BOOST_CHECK_EQUAL(oa.bytes_written(), buf.size());
+ }
+
+ {
+ auto ia = Utils::MemcpyIArchive{Utils::make_span(buf)};
+ OpVec out1, out2;
+ ia >> out1;
+ ia >> out2;
+
+ BOOST_CHECK_EQUAL(ia.bytes_read(), buf.size());
+
+ BOOST_CHECK(out1 == active);
+ BOOST_CHECK(out2 == inactive);
+ }
+}
+
+BOOST_AUTO_TEST_CASE(non_trivial_packing) {
+ static_assert(not std::is_trivially_copyable::value, "");
+
+ std::vector buf(2 * Utils::MemcpyOArchive::packing_size());
+
+ auto const active = NonTrivial{Utils::Vector3d{1., 2., 3.}};
+ auto const inactive = NonTrivial{boost::none};
+
+ {
+ auto oa = Utils::MemcpyOArchive{Utils::make_span(buf)};
+ auto in1 = active;
+ auto in2 = inactive;
+ oa << in1;
+ oa << in2;
+
+ BOOST_CHECK_EQUAL(oa.bytes_written(), buf.size());
+ }
+
+ {
+ auto ia = Utils::MemcpyIArchive{Utils::make_span(buf)};
+ NonTrivial out1, out2;
+ ia >> out1;
+ ia >> out2;
+
+ BOOST_CHECK_EQUAL(ia.bytes_read(), buf.size());
+
+ BOOST_CHECK(out1.ov == active.ov);
+ BOOST_CHECK(out2.ov == inactive.ov);
+ }
+}
\ No newline at end of file