C++ persistent containers
Our goal for the libpmemobj C++ bindings is to create a friendly and less error prone API for persistent memory programming. Even with persistent memory pool allocators, convenient interface for creating and managing transactions, auto-snapshotting class templates and smart persistent pointers, designing an application with persistent memory usage may still prove challenging without a plethora of niceties that the C++ programmers are used to.
The natural step forward to make persistent programming easier, is to provide programmers with efficient and useful containers.
Code reuse is a common programming principle, and we took it to heart when
sometime ago we experimented with persistent memory containers by introducing
custom persistent allocator for
libc++ STL containers. You can read more about
this approach in this blog post.
As it turns out, there are some hard to overcome obstacles with using existing
STL containers. The two main downsides are:
- Implementation details: STL containers don’t use algorithms optimal from persistent memory programming point of view. PMEM containers should have durability and consistency properties, while not every STL method guarantee strong exception safety. Persistent memory containers should be designed with awareness of fragmentation limitations.
- Memory layout: STL does not guarantee that container layout will remain unchanged in new library versions.
We ultimately came to the conclusion that implementing containers with optimized on-media layouts and algorithms to fully exploit persistent memory potential should be one of our primary focuses. Their methods should guarantee atomicity, consistency and durability.
Beside specific internal implementation details, PMEM containers will have
well-known STL-like interface and will work with STL algorithms. Since they
libpmemobj-cpp project, the goal is to implement them with
libpmemobj-cpp bindings and make them easily accessible with its
Because of API similarities with STL, we are reusing ideas behind
container tests and simply port them to our implementation and project.
C++ language restrictions and the persistent memory programming paradigm imply some serious restrictions on objects, which may be stored on persistent medium. This topic deserves a separate blog post and should cover questions about language-based undefined behavior, objects lifetime and compiler compatibility - just to name a few of them.
You can expect blog post about this in nearby future, but for now I will just list the most important ideas:
- Implicit creation of objects stored in persistent medium (C++ standard states that lifetime of an object is a runtime property)
- memcpy-ing objects which don’t satisfy requirements of is_trivially_copyable (we are not calling neither constructors nor destructors during snapshotting memory areas)
- Objects representation (layout) might differ between compilers/compiler
flags/ABI. C++11 provides type trait
StandardLayoutTypeis a very restrictive set of requirements (see here).
- Usage of volatile memory pointers in persistent structures should be treated as a design error.
You can read more about template parameter type restrictions for
pmem::obj::persistent_ptr<T> class in PMDK C++ bindings doxygen documentation.
Implementing containers from scratch will be a long effort, hence our decision about specific containers types to be implemented and the order of their implementation is quite important.
We have already finished the first container - an
array. It is included in
pmem::obj::experimental namespace and you can read more about it in
this blog post.
We have been working on implementation of
vector for some time and the next
scheduled containers are
unordered_map. We might change
this order, because we are considering an implementation of optimized vector,
based on idea of
std::colony container which will internally use
pmem::obj::vector. This approach will reduce fragmentation factor, and gain
performance benefits similar to those presented in C++ standard library
Persistent memory is an emerging technology, and as we mentioned in one of the previous blog posts, we truly believe that it will be paradigm shifting.
At this point, it is hard to predict specific use-cases for persistent memory containers. They can be used as persistent scratch pads, extension for in-memory databases or fast and flexible data storage. We also believe, that functional programming ideas can be used in implementation of persistent memory programming models.
We will continue our research efforts in area of purely functional persistent
data structures, concepts of filters and further optimizations (like
pmem::obj::slice idea, also described in blog post about array).