Code Sample: Convert Your Transient C++ Map Application to Use Persistent Memory

File(s):

Download
License:3-Clause BSD License
Optimized for... 
Operating System:Linux* kernel version 4.3 or higher
Hardware:Hardware: Intel® Optane ™ DC Persistent memory and 2nd generation Intel® Xeon® Scalable processor Emulated:
See How to Emulate Persistent Memory Using Dynamic Random-access Memory (DRAM)
Software:
(Programming Language, tool, IDE, Framework)
C++ Compiler, Persistent Memory Development Kit (PMDK) libraries
Prerequisites:Familiarity with C++

Introduction

C++ maps, which consist of a set of elements made up of key/value pairs, are associative containers included in the C++ Standard Template Library (STL). This tutorial shows how to take an application that implements a C++ map using transient (volatile) memory and modify it to use persistent memory. The benefits of storing a map in persistent memory include fast application reboot and improved performance, where data is byte-addressable and accessible at memory bus speeds. A full code sample is available on GitHub*.

Prerequisites

This article assumes you have a basic understanding of persistent memory concepts and are familiar with features of the Persistent Memory Development Kit (PMDK). If not, visit the Intel® Developer Zone Persistent Memory site, where you’ll find the information you need to get started.

Code Sample Design

The code sample contains a tree map implementation that uses traditional new and delete operations to store data in dynamic random-access memory (DRAM). We show how to modify this program using APIs from the PMDK libpmemobj-cpp library to create a persistent memory version of the application. You can apply the same techniques to your own data structures. Finally, there is a command-line interface (CLI) tool that allows us to test both implementations quickly. This is easily done because both tree maps have the same interfaces and the tree map to be tested is chosen via a command line argument.

Data Structures

Below is a Unified Modeling Language (UML) class diagram for the transient tree map. You’ll see it’s a simple design with the common methods you would expect. The persistent tree map has exactly the same interfaces and can be considered as a drop-in replacement.

Tree map of directory

A Code Walk-Through

In this section, we show how to modify the transient implementation to interface with persistent memory using the libpmemobj library.

Insert New Element

We’ll first insert a new value into the transient implementation tree map:

template <typename... Args>
int
insert_new(key_type key, const Args &... args)
{
	return insert(key, new T(args...));
}


Then we modify the implementation to store the data in persistent memory:

template <typename... Args>
	int
	insert_new(key_type key, const Args &... args)
	{
		auto pop = nvobj::pool_by_vptr(this);
		nvobj::transaction::run(pop, [&] {
			return insert(key, nvobj::make_persistent<T>(args...));
		});

		return -1;
	}

As you see, the signature of the method remains the same. Three key changes include:

  • Get access to the pool object using:
    nvobj::pool_by_vptr(this)
  • Wrap our insert function with a transaction:
    nvobj::transaction::run(pop, [&] { … });

    This ensures the cache lines modified during insertion into the tree map are efficiently flushed from the CPU cache to guarantee the data makes it to persistent memory. Additionally, using a transaction ensures that the data structure stays consistent in the event of a process crash in the middle of an update.

  • Rather than using the new operator to allocate the memory, we use:
    nvobj::make_persistent<T>(args...)
    This performs a memory allocation on persistent memory. It then returns a persistent memory pointer.

Here we’ve demonstrated the modifications required to allocate and use persistent memory to store a new value.

Remove Element

For our second example, we show how to remove a map element when it is stored in persistent memory.

Below is the conventional approach where free removes the key/value pair from the tree and then deletes the memory using the pointer returned by the remove method.

int
remove_free(key_type key)
{
	delete remove(key);
	return 0;
}

Below is the implementation for removing persistent data:

int
remove_free(key_type key)
{
	auto pop = nvobj::pool_by_vptr(this);
	nvobj::transaction::run(
		pop, [&] { nvobj::delete_persistent<T>(remove(key)); });
	return 0;
}

Again, we make three changes: getting our pool object; wrapping our remove method inside a transaction for the reasons described above; and using nvobj::delete_persistent<T>(remove(key) instead of free to release the persistent memory so that it can be used again.

Compile and Run

The libpmemobj-cpp library, including code examples, can be built on Linux* by following these instructions:

$ mkdir build
$ cd build
$ cmake ..
$ make
$ make install

More details on how to build libpmemobj-cpp can be found on the GitHub* page C++ Bindings For libpmemobj.

After the binary is compiled, test the program using map_cli to store, retrieve, and delete a key/value pair from the tree map:

Store a key/value pair:

./map_cli pool persistent insert_new my_key my_value

Retrieve the value using the key:

./map_cli pool persistent get my_key

Delete the value using the key:

./map_cli pool persistent remove_free my_key

Note the usage of remove_free. It calls the remove_free method, which both removes the value from my_key and then deallocates the memory. Calling remove will only remove the value from the tree; therefore, it is important to use the correct method in your application to avoid potential memory leaks.

Summary

We used a simple CLI application to interface with both a transient and persistent tree map implementation. Both implementations are provided and show the similarities with minimal code changes to guarantee persistent data. Download the sample code from Github, and visit the Persistent Memory site on Intel Developer Zone to learn more about persistent memory programming.

About the Authors

Thai Le is a software engineer focusing on cloud computing and performance computing analysis at Intel Corporation.

Steven Briscoe is an Industry Technical Specialist driving the adoption of Persistent Memory with Cloud Service Providers at Intel Corporation.

References

For more complete information about compiler optimizations, see our Optimization Notice.