This commit is contained in:
greg
2019-03-02 21:27:48 -05:00
parent 806ca72ab7
commit c53ffb5d5d
2 changed files with 97 additions and 18 deletions
Vendored
+78 -1
View File
@@ -98,7 +98,84 @@ This is already looking pretty good. For large hash_maps, the parallel_flat_hash
But there is another aspect of the inherent parallelism of the parallel_hash_map which is interesting to explore. As we know, typical hash maps cannot be modified from multiple threads without explicit synchronization. And bracketing write accesses to a shared hash_map with synchronization primitives, such as mutexes, can reduce the concurrency of our program, and even cause deadlocks.
Because the parallel_hash_map is built of sixteen separate subtables, it posesses some intrinsic parallelism. Indeed, suppose you can make sure that different threads will use different subtables, you would be able to insert
Because the parallel_hash_map is built of sixteen separate subtables, it posesses some intrinsic parallelism. Indeed, suppose you can make sure that different threads will use different subtables, you would be able to insert into the same parallel_hash_map at the same time from the different threads without any locking.
So, if you can iterate over the values you want to insert into the hash table, the idea is that each thread will iterate over all values, and then for each value:
1. compute the hash for that value
2. compute the subtable index for that hash
3. if the subtable index is the assigned to this thread, do nothing and continue to the next value, otherwise insert the value
Here is the code for the single-threaded insert:
```c++
template <class HT>
void _fill_random_inner(int64_t cnt, HT &hash, RSU &rsu)
{
for (int64_t i=0; i<cnt; ++i)
{
hash.insert(typename HT::value_type(rsu.next(), 0));
++num_keys[0];
}
}
```
and here is the code for the multi-threaded insert:
```c++
// --------------------------------------------------------------------------
template <class HT>
struct TD
{
int64_t thread_idx;
int64_t num_threads;
int64_t cnt;
HT &hash;
RSU rsu; // generates a random sequence of unique integers
};
// --------------------------------------------------------------------------
template <class HT>
void _fill_random_inner_thr(TD<HT> td)
{
typename HT::hasher hasher; // get hasher object from the hash table
size_t modulo = td.hash.subcnt() / td.num_threads; // subcnt() returns the number of subtables
for (int64_t i=0; i<td.cnt; ++i) // iterate over all values
{
unsigned int key = td.rsu.next(); // get next key to insert
size_t hash = hasher(key); // compute its hash
size_t idx = td.hash.subidx(hash); // compute the subtable index for this hash
if (idx / modulo == td.thread_idx) // if the subtable is suitable for this thread
{
td.hash.insert(typename HT::value_type(key, 0)); // insert the value
++(num_keys[td.thread_idx]); // increment count of inserted values
}
}
}
// --------------------------------------------------------------------------
template <class HT>
void _fill_random_inner_mt(int64_t cnt, HT &hash, RSU &rsu)
{
constexpr int64_t num_threads = 8; // has to be a power of two
std::unique_ptr<std::thread> threads[num_threads];
for (int64_t i=0; i<num_threads; ++i)
{
TD<HT> td {i, num_threads, cnt, hash, rsu};
threads[i].reset(new std::thread(_fill_random_inner_thr<HT>, td));
}
// rsu passed by value to threads... we need to increment the reference object
for (int64_t i=0; i<cnt; ++i)
rsu.next();
for (int64_t i=0; i<num_threads; ++i)
threads[i]->join();
}
```
+19 -17
View File
@@ -58,7 +58,7 @@ private:
// --------------------------------------------------------------------------
// from: https://github.com/preshing/RandomSequence
// --------------------------------------------------------------------------
class RandomSequenceOfUnique
class RSU
{
private:
unsigned int m_index;
@@ -74,7 +74,7 @@ private:
}
public:
RandomSequenceOfUnique(unsigned int seedBase, unsigned int seedOffset)
RSU(unsigned int seedBase, unsigned int seedOffset)
{
m_index = permuteQPR(permuteQPR(seedBase) + 0x682f0161);
m_intermediateOffset = permuteQPR(permuteQPR(seedOffset) + 0x46790905);
@@ -146,7 +146,7 @@ static const char *test = "random";
// --------------------------------------------------------------------------
template <class HT>
void _fill_random_inner(int64_t cnt, HT &hash, RandomSequenceOfUnique &rsu)
void _fill_random_inner(int64_t cnt, HT &hash, RSU &rsu)
{
for (int64_t i=0; i<cnt; ++i)
{
@@ -163,7 +163,7 @@ struct TD
int64_t num_threads;
int64_t cnt;
HT &hash;
RandomSequenceOfUnique rsu;
RSU rsu; // generates a random sequence of unique integers
};
// --------------------------------------------------------------------------
@@ -171,17 +171,18 @@ template <class HT>
void _fill_random_inner_thr(TD<HT> td)
{
#ifdef MT_SUPPORT
typename HT::hasher hasher;
size_t modulo = td.hash.subcnt() / td.num_threads;
for (int64_t i=0; i<td.cnt; ++i)
typename HT::hasher hasher; // get hasher object from the hash table
size_t modulo = td.hash.subcnt() / td.num_threads; // subcnt() returns the number of subtables
for (int64_t i=0; i<td.cnt; ++i) // iterate over all values
{
unsigned int key = td.rsu.next();
size_t hash = hasher(key);
size_t idx = td.hash.subidx(hash);
if (idx / modulo == td.thread_idx)
unsigned int key = td.rsu.next(); // get next key to insert
size_t hash = hasher(key); // compute its hash
size_t idx = td.hash.subidx(hash); // compute the subtable index for this hash
if (idx / modulo == td.thread_idx) // if the subtable is suitable for this thread
{
td.hash.insert(typename HT::value_type(key, 0));
++(num_keys[td.thread_idx]);
td.hash.insert(typename HT::value_type(key, 0)); // insert the value
++(num_keys[td.thread_idx]); // increment count of inserted values
}
}
#endif
@@ -189,17 +190,18 @@ void _fill_random_inner_thr(TD<HT> td)
// --------------------------------------------------------------------------
template <class HT>
void _fill_random_inner_mt(int64_t cnt, HT &hash, RandomSequenceOfUnique &rsu)
void _fill_random_inner_mt(int64_t cnt, HT &hash, RSU &rsu)
{
constexpr int64_t num_threads = 8;
constexpr int64_t num_threads = 8; // has to be a power of two
std::unique_ptr<std::thread> threads[num_threads];
for (int64_t i=0; i<num_threads; ++i)
{
TD<HT> td {i, num_threads, cnt, hash, rsu};
assert(&td.hash == &hash);
threads[i].reset(new std::thread(_fill_random_inner_thr<HT>, td));
}
// rsu passed by value to threads... we need to increment the reference object
for (int64_t i=0; i<cnt; ++i)
rsu.next();
@@ -222,7 +224,7 @@ Timer _fill_random2(int64_t cnt, HT &hash)
{
test = "random";
unsigned int seed = 76687;
RandomSequenceOfUnique rsu(seed, seed + 1);
RSU rsu(seed, seed + 1);
Timer timer(true);
const int64_t num_loops = 10;