C++ persistent containers - array
[Note: pmem::obj::array<> is no longer experimental. The rest of the information in this blog post is still accurate.]
Until now, our C++ bindings were missing one important component - persistent
containers. In 1.5 release we have introduced the first one - pmem::obj::array.
This container is currently placed in
experimental namespace and folder (this means
that both API and layout can change). It has almost the same functionality as
from C++11 but takes care of adding elements to a transaction. Once experimental status
will be dropped, it will also guarantee a stable in-memory layout (it will be the same for all compilers).
std::array is the same, except for the following:
pmem::obj::arraydefines range() method (described below)
pmem::obj::arraydoes not mark any non-const function as
noexcept- elements must be added to a transaction which could result in an exception
If you want to store a sequence of objects, whose length is known at compile time,
you should use
pmem::obj::array. In contrast to plain arrays,
automatically adds modified elements to the enclosing transaction.
Let’s start with a simple example:
As seen above,
pmem::obj::array can be used just like an ordinary
For iterating over it you can use indexing operator, range-based for loops or
iterators. Array can also be processed using
If there is an active transaction, elements (accessed using any of the listed
methods) are snapshotted. In case of iterators returned by begin() and end()
snapshotting happens during iterator dereferencing. Of course, snapshotting is
done only for mutable elements. In case of
const iterators or
versions of indexing operator, nothing is added to a transaction. That’s why
it is extremely important to use
const functions (cbegin(), cend(), etc.)
whenever possible. It will reduce number of snapshots and can significantly
reduce the performance impact of transactions.
In cases where loop is known to modify several consecutive elements in the array, a bulk-snapshot optimization can be performed using a special range() function which returns an instance of pmem::obj::slice struct. This structure provides interface to access sequence of objects - it implements indexing operators as well as begin() and end() methods (plus const and reverse variants).
Here’s sample usage:
This examples shows that
pmem::obj::slice can be iterated the same way as
The difference is that elements are not snapshotted one by one, instead they are
added to a transaction in bulk. Let’s analyze what is happening in case of first
for loop in the above example. First, notice that the third argument in
function is equal to
2. This means that elements will be snapshotted in pairs.
At the beginning of the loop, first two elements in the array will be already
added to a transaction (this is done in
range() method), so that it will not
have to be done in the first and the second iteration. In the third iteration,
elements at indexes 2 and 3 will be snapshotted, and so on. Assuming size of
array equal to 6, number of snapshots will be thus equal to 3. This mechanism is
also described here.
If all elements (or most of them) are expected to be modified,
range() can be called like this:
This will add the entire array to a transaction once.
There is no universal rule, when to use
range(). Performance gain will depend
on snapshot size, element type and type of workload. Usage of this method should
be carefully thought out or benchmarked.
pmem::obj::array and pmem::obj::persistent_ptr
Above examples used
pmem::obj::array as a struct member but it is also possible
to have direct
pmem::obj::persistent_ptr to it. There is, however, one thing users
should be aware of while using this approach. Consider the following code:
As stated in the comment, initializing
with list of values is only possible since C++17. This is because
std::array, is an aggregate type and needs special initialization syntax (brace
initialization must be used). The problem is that, in order to support aggregate initialization,
we must check whether a type is an aggregate in
this check is only available since C++17.
To summarize, if you need to store fixed-length array in persistent memory you should
pmem::obj::array. This is currently the only persistent container
in our library. However, we are working on
so you can expect our containers collection to grow in the near future.