|
| concurrent_hash_map () |
| Construct empty table.
|
|
| concurrent_hash_map (size_type n) |
| Construct empty table with n preallocated buckets. More...
|
|
| concurrent_hash_map (const concurrent_hash_map &table) |
| Copy constructor.
|
|
| concurrent_hash_map (concurrent_hash_map &&table) |
| Move constructor.
|
|
template<typename I > |
| concurrent_hash_map (I first, I last) |
| Construction table with copying iteration range.
|
|
| concurrent_hash_map (std::initializer_list< value_type > il) |
| Construct table with initializer list.
|
|
void | runtime_initialize () |
| Initialize persistent concurrent hash map after process restart. More...
|
|
concurrent_hash_map & | operator= (const concurrent_hash_map &table) |
| Assignment Not thread safe. More...
|
|
concurrent_hash_map & | operator= (std::initializer_list< value_type > il) |
| Assignment Not thread safe. More...
|
|
void | rehash (size_type n=0) |
| Rehashes and optionally resizes the whole table. More...
|
|
void | clear () |
| Clear hash map content Not thread safe. More...
|
|
void | free_data () |
| Destroys the concurrent_hash_map. More...
|
|
| ~concurrent_hash_map () |
| free_data should be called before concurrent_hash_map destructor is called. More...
|
|
iterator | begin () |
|
iterator | end () |
|
const_iterator | begin () const |
|
const_iterator | end () const |
|
size_type | size () const |
|
bool | empty () const |
|
size_type | max_size () const |
| Upper bound on size.
|
|
size_type | bucket_count () const |
|
void | swap (concurrent_hash_map &table) |
| Swap two instances. More...
|
|
size_type | count (const Key &key) const |
|
template<typename K , typename = typename std::enable_if< concurrent_hash_map_internal:: has_transparent_key_equal<hasher>::value, K>::type> |
size_type | count (const K &key) const |
| This overload only participates in overload resolution if the qualified-id Hash::transparent_key_equal is valid and denotes a type. More...
|
|
bool | find (const_accessor &result, const Key &key) const |
| Find item and acquire a read lock on the item. More...
|
|
template<typename K , typename = typename std::enable_if< concurrent_hash_map_internal:: has_transparent_key_equal<hasher>::value, K>::type> |
bool | find (const_accessor &result, const K &key) const |
| Find item and acquire a read lock on the item. More...
|
|
bool | find (accessor &result, const Key &key) |
| Find item and acquire a write lock on the item. More...
|
|
template<typename K , typename = typename std::enable_if< concurrent_hash_map_internal:: has_transparent_key_equal<hasher>::value, K>::type> |
bool | find (accessor &result, const K &key) |
| Find item and acquire a write lock on the item. More...
|
|
bool | insert (const_accessor &result, const Key &key) |
| Insert item (if not already present) and acquire a read lock on the item. More...
|
|
bool | insert (accessor &result, const Key &key) |
| Insert item (if not already present) and acquire a write lock on the item. More...
|
|
bool | insert (const_accessor &result, const value_type &value) |
| Insert item by copying if there is no such key present already and acquire a read lock on the item. More...
|
|
bool | insert (accessor &result, const value_type &value) |
| Insert item by copying if there is no such key present already and acquire a write lock on the item. More...
|
|
bool | insert (const value_type &value) |
| Insert item by copying if there is no such key present already. More...
|
|
bool | insert (const_accessor &result, value_type &&value) |
| Insert item by copying if there is no such key present already and acquire a read lock on the item. More...
|
|
bool | insert (accessor &result, value_type &&value) |
| Insert item by copying if there is no such key present already and acquire a write lock on the item. More...
|
|
bool | insert (value_type &&value) |
| Insert item by copying if there is no such key present already. More...
|
|
template<typename I > |
void | insert (I first, I last) |
| Insert range [first, last) More...
|
|
void | insert (std::initializer_list< value_type > il) |
| Insert initializer list. More...
|
|
template<typename M > |
bool | insert_or_assign (const key_type &key, M &&obj) |
| Inserts item if there is no such key present already, assigns provided value otherwise. More...
|
|
template<typename M > |
bool | insert_or_assign (key_type &&key, M &&obj) |
| Inserts item if there is no such key present already, assigns provided value otherwise. More...
|
|
template<typename K , typename M , typename = typename std::enable_if< concurrent_hash_map_internal::has_transparent_key_equal< hasher>::value && std::is_constructible<key_type, K>::value, K>::type> |
bool | insert_or_assign (K &&key, M &&obj) |
| Inserts item if there is no such key-comparable type present already, assigns provided value otherwise. More...
|
|
bool | erase (const Key &key) |
| Remove element with corresponding key. More...
|
|
pobj_defrag_result | defragment (double start_percent=0, double amount_percent=100) |
| Defragment the given (by 'start_percent' and 'amount_percent') part of buckets of the hash map. More...
|
|
template<typename K , typename = typename std::enable_if< concurrent_hash_map_internal:: has_transparent_key_equal<hasher>::value, K>::type> |
bool | erase (const K &key) |
| Remove element with corresponding key. More...
|
|
template<typename Key, typename T, typename Hash, typename KeyEqual, typename MutexType, typename ScopedLockType>
class pmem::obj::concurrent_hash_map< Key, T, Hash, KeyEqual, MutexType, ScopedLockType >
Persistent memory aware implementation of Intel TBB concurrent_hash_map.
The implementation is based on a concurrent hash table algorithm (https://arxiv.org/ftp/arxiv/papers/1509/1509.02235.pdf) where elements are assigned to buckets based on a hash code calculated from a key. In addition to concurrent find, insert, and erase operations, the algorithm employs resizing and on-demand per-bucket rehashing. The hash table consists of an array of buckets, and each bucket consists of a list of nodes and a read-write lock to control concurrent access by multiple threads.
Each time, the pool with concurrent_hash_map is being opened, the concurrent_hash_map requires runtime_initialize() to be called (in order to recalculate mask and restore the size).
find(), insert(), erase() (and all overloads) are guaranteed to be thread-safe.
When a thread holds accessor to an element with a certain key, it is not allowed to call find, insert nor erase with that key.
MutexType defines type of read write lock used in concurrent_hash_map. ScopedLockType defines a mutex wrapper that provides RAII-style mechanism for owning a mutex. It should implement following methods and constructors: ScopedLockType() ScopedLockType(rw_mutex_type &m, bool write = true) void acquire(rw_mutex_type &m, bool write) void release() bool try_acquire(rw_mutex_type &m, bool write)
and optionally: bool upgrade_to_writer() bool downgrade_to_reader() bool is_writer (variable)
Implementing all optional methods and supplying is_writer variable can improve performance if MutexType supports efficient upgrading and downgrading operations.
Testing note: In some case, helgrind and drd might report lock ordering errors for concurrent_hash_map. This might happen when calling find, insert or erase while already holding an accessor to some element.
The typical usage example would be:
#include <iostream>
#include <vector>
const int THREADS_NUM = 30;
struct root {
};
int
main(int argc, char *argv[])
{
bool remove_hashmap = false;
try {
if (argc < 2)
std::cerr << "usage: " << argv[0]
<< " file-name [remove_hashmap]" << std::endl;
auto path = argv[1];
if (argc == 3)
remove_hashmap = std::string(argv[2]) == "1";
try {
std::cerr << e.what() << std::endl;
return -1;
}
auto &r = pop.
root()->pptr;
if (r == nullptr) {
r = make_persistent<hashmap_type>();
});
r->runtime_initialize();
} else {
r->runtime_initialize();
try {
r->defragment();
std::cerr << "Defragmentation exception: "
<< e.what() << std::endl;
throw;
}
}
auto &map = *r;
std::cout << map.size() << std::endl;
std::vector<std::thread> threads;
threads.reserve(static_cast<size_t>(THREADS_NUM));
for (int i = 0; i < THREADS_NUM / 3; ++i) {
threads.emplace_back([&]() {
for (int i = 0; i < 10 * THREADS_NUM; ++i) {
map.insert(
hashmap_type::value_type(i, i));
}
});
}
for (int i = 0; i < THREADS_NUM / 3; ++i) {
threads.emplace_back([&]() {
for (int i = 0; i < 10 * THREADS_NUM; ++i) {
map.erase(i);
}
});
}
for (int i = 0; i < THREADS_NUM / 3; ++i) {
threads.emplace_back([&]() {
for (int i = 0; i < 10 * THREADS_NUM; ++i) {
hashmap_type::accessor acc;
bool res = map.find(acc, i);
if (res) {
assert(acc->first == i);
assert(acc->second >= i);
acc->second.get_rw() += 1;
pop.persist(acc->second);
}
}
});
}
for (auto &t : threads) {
t.join();
}
try {
map.defragment();
std::cerr << "Defragmentation exception: " << e.what()
<< std::endl;
throw;
std::cerr << "Defragmentation exception: " << e.what()
<< std::endl;
throw;
} catch (const std::range_error &e) {
std::cerr << "Defragmentation exception: " << e.what()
<< std::endl;
throw;
} catch (const std::runtime_error &e) {
std::cerr << "Defragmentation exception: " << e.what()
<< std::endl;
throw;
}
if (remove_hashmap) {
try {
map.clear();
std::cerr << "Clear exception: " << e.what()
<< std::endl;
throw;
std::cerr << "Clear exception: " << e.what()
<< std::endl;
throw;
}
map.free_data();
delete_persistent<hashmap_type>(r);
r = nullptr;
});
}
pop.close();
} catch (std::exception &e) {
std::cerr << "Exception occured: " << e.what() << std::endl;
try {
pop.close();
} catch (const std::logic_error &e) {
std::cerr << "Exception: " << e.what() << std::endl;
}
return -1;
}
return 0;
}
The example of storing strings without necessity of using transactions would be:
#include <iostream>
#include <string>
#include <vector>
using hashmap_type =
const int THREADS_NUM = 30;
struct root {
};
int
main(int argc, char *argv[])
{
bool remove_hashmap = false;
int retval = 0;
try {
if (argc < 2) {
std::cerr
<< "usage: " << argv[0]
<< " file-name [remove_hashmap]" << std::endl
<< "Before running this example, run:"
<< std::endl
<< "pmempool create obj --layout=\"cmap_string\" --size 1G path_to_a_pool"
<< std::endl;
return -1;
}
auto path = argv[1];
if (argc == 3)
remove_hashmap = std::string(argv[2]) == "1";
try {
std::cerr << e.what() << std::endl;
return -1;
}
auto &r = pop.
root()->pptr;
if (r == nullptr) {
r = pmem::obj::make_persistent<hashmap_type>();
});
r->runtime_initialize();
} else {
r->runtime_initialize();
try {
r->defragment();
std::cerr << "Defragmentation exception: "
<< e.what() << std::endl;
throw;
}
}
auto &map = *r;
std::cout << " Number of elements at application startup: "
<< map.size() << std::endl;
std::vector<std::thread> threads;
threads.reserve(static_cast<size_t>(THREADS_NUM));
for (int j = 0; j < THREADS_NUM / 3; ++j) {
threads.emplace_back([&]() {
for (int i = 0; i < 10 * THREADS_NUM; ++i) {
map.insert_or_assign(i,
std::to_string(i));
}
});
}
for (int j = 0; j < THREADS_NUM / 3; ++j) {
threads.emplace_back([&]() {
for (int i = 0; i < 10 * THREADS_NUM; ++i) {
hashmap_type::const_accessor acc;
bool res = map.find(acc, i);
if (res) {
assert(acc->first == i);
*element = &acc->second;
std::cout << element->
c_str()
<< std::endl;
}
}
});
}
for (int j = 0; j < THREADS_NUM / 3; ++j) {
threads.emplace_back([&]() {
for (int i = 0; i < 10 * THREADS_NUM; ++i) {
map.erase(i);
}
});
}
for (auto &t : threads) {
t.join();
}
try {
map.defragment();
std::cerr << "Defragmentation exception: " << e.what()
<< std::endl;
throw;
std::cerr << "Defragmentation exception: " << e.what()
<< std::endl;
throw;
} catch (const std::range_error &e) {
std::cerr << "Defragmentation exception: " << e.what()
<< std::endl;
throw;
} catch (const std::runtime_error &e) {
std::cerr << "Defragmentation exception: " << e.what()
<< std::endl;
throw;
}
if (remove_hashmap) {
try {
map.clear();
std::cerr << "Clear exception: " << e.what()
<< std::endl;
throw;
std::cerr << "Clear exception: " << e.what()
<< std::endl;
throw;
}
map.free_data();
pmem::obj::delete_persistent<hashmap_type>(r);
r = nullptr;
});
}
} catch (std::exception &e) {
std::cerr << "Exception occured: " << e.what() << std::endl;
retval = -1;
}
try {
pop.close();
} catch (const std::logic_error &e) {
std::cerr << "Exception: " << e.what() << std::endl;
retval = -2;
}
return retval;
}