Skip to content

Commit

Permalink
cleanup
Browse files Browse the repository at this point in the history
  • Loading branch information
gblelloch committed May 8, 2024
1 parent 1fcdde0 commit 583624a
Show file tree
Hide file tree
Showing 5 changed files with 93 additions and 48 deletions.
2 changes: 1 addition & 1 deletion examples/convex_hull_3d.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ int main(int argc, char* argv[]) {

parlay::internal::timer t("Time");
parlay::sequence<tri> result;
for (int i=0; i < 5; i++) {
for (int i=0; i < 20; i++) {
result = convex_hull_3d(points);
t.next("convex_hull_3d");
}
Expand Down
35 changes: 21 additions & 14 deletions examples/convex_hull_3d.h
Original file line number Diff line number Diff line change
@@ -1,10 +1,12 @@
#include <utility>
#include <array>
#include <memory>
#include <optional>
#include <tuple>
#include <utility>

#include <parlay/parallel.h>
#include <parlay/primitives.h>
#include <parlay/sequence.h>
#include <parlay/utilities.h>

#include "hash_map.h"

Expand Down Expand Up @@ -81,11 +83,13 @@ struct Convex_Hull_3d {
// convex hull in 3d space
void process_ridge(triangle_ptr& t1, edge r, triangle_ptr& t2) {
if (t1->conflicts.size() == 0 && t2->conflicts.size() == 0) {
t1 = t2 = nullptr; // allow to be reclaimed
return;
} else if (min_conflicts(t2) == min_conflicts(t1)) {
// H \ {t1, t2}
convex_hull.remove(t1->t);
convex_hull.remove(t2->t);
t1 = t2 = nullptr; // allow to be reclaimed
} else if (min_conflicts(t2) < min_conflicts(t1)) {
process_ridge(t2, r, t1);
} else {
Expand All @@ -107,16 +111,18 @@ struct Convex_Hull_3d {
// H <- (H \ {t1}) U {t}
convex_hull.remove(t1->t);
convex_hull.insert(t, true);

t1 = nullptr; // allow to be reclaimed

auto check_edge = [&] (edge e, triangle_ptr& tp) {
auto key = (e[0] < e[1]) ? e : edge{e[1], e[0]};
if (map_facets.insert(key, tp)) return;
auto tt = map_facets.remove(key).value();
process_ridge(tp, e, tt);};

auto t_new_1 = t_new; auto t_new_2 = t_new;
parlay::par_do3([&] {process_ridge(t_new, r, t2);},
[&] {check_edge(edge{r[0], pid}, t_new);},
[&] {check_edge(edge{r[1], pid}, t_new);});
[&] {check_edge(edge{r[0], pid}, t_new_1);},
[&] {check_edge(edge{r[1], pid}, t_new_2);});
}
}

Expand Down Expand Up @@ -157,17 +163,18 @@ struct Convex_Hull_3d {
});

// The 6 ridges of the initial tetrahedron along with their neighboring triangles
std::tuple<int, int, edge> ridges[6] =
{{0, 1, edge{1, 2}},
{0, 2, edge{0, 2}},
{0, 3, edge{0, 1}},
{1, 2, edge{2, 3}},
{1, 3, edge{1, 3}},
{2, 3, edge{0, 3}}};
std::tuple<triangle_ptr, triangle_ptr, edge> ridges[6] =
{{t[0], t[1], edge{1, 2}},
{t[0], t[2], edge{0, 2}},
{t[0], t[3], edge{0, 1}},
{t[1], t[2], edge{2, 3}},
{t[1], t[3], edge{1, 3}},
{t[2], t[3], edge{0, 3}}};
for (auto& x : t) x = nullptr; // allow to be reclaimed

parlay::parallel_for(0, 6, [&](auto i) {
auto [t1_idx, t2_idx, e] = ridges[i];
process_ridge(t[t1_idx], e, t[t2_idx]); });
auto [t1, t2, e] = ridges[i];
process_ridge(t1, e, t2); }, 1);
}
};

Expand Down
6 changes: 4 additions & 2 deletions examples/delaunay.h
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
#include <utility>
#include <array>
#include <memory>
#include <optional>
#include <utility>

#include <parlay/parallel.h>
#include <parlay/primitives.h>
#include <parlay/sequence.h>
#include <parlay/utilities.h>

#include "hash_map.h"

Expand Down Expand Up @@ -105,6 +106,7 @@ struct Delaunay {
auto key = (e[0] < e[1]) ? e : edge{e[1], e[0]};
if (edges.insert(key, tp)) return;
auto tt = *edges.remove(key);
//auto tt = *edges.find(key);
process_edge(tp, e, tt);};
auto ta1 = t1; auto tb1 = t1;
parlay::par_do3([&] {check_edge(edge{p, e[0]}, ta1);},
Expand Down
1 change: 1 addition & 0 deletions examples/hash_map.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ int main(int argc, char* argv[]) {
parlay::for_each(pairs, [&] (auto p) {
m.find(p.second);});
t.next("hash_map : find");
keys = m.keys();
}

std::cout << "number of unique keys: " << keys.size() << std::endl;
Expand Down
97 changes: 66 additions & 31 deletions examples/hash_map.h
Original file line number Diff line number Diff line change
@@ -1,10 +1,9 @@
#include <cstddef>

#include <algorithm>
#include <atomic>
#include <cstddef>
#include <functional>
#include <optional>
#include <iostream>
#include <optional>
#include <utility>

#include <parlay/primitives.h>
Expand All @@ -15,11 +14,14 @@
// Supports concurrent linearizable, insert, find and remove.
// size(), keys() don't linearize with updates.
// Requires the capacity to be specified on construction.
// No more than the capacity distinct keys can ever be added.
// Uses linear probing, so no more than the capacity distinct keys can ever be added.
// Once a key is added, removing it will empty the value and mark
// the key as deleted, but only a value with the same key can use the
// same slot (i.e. it still counts towards the capacity).
// It uses locks, but holds them very briefly.
//
// Implemented with uses sequence locks to ensure values are updated
// and read atomically.
// Finds are wait free (as long as table is not overfull)
// **************************************************************

template <typename K,
Expand All @@ -36,15 +38,30 @@ struct hash_map {
Hash hash;
Equal equal;

enum state : char {empty, full, locked, tomb};
struct entry {
std::atomic<state> status;
std::atomic<size_t> seq_num;
K key;
V value;
entry() : status(empty) {}
entry() : seq_num(0) {}
};
parlay::sequence<entry> H;

// uses two low order bits of seq_num to indicate the state
// 0 = available (only true with 0 sequence number)
// 1 = full
// 2 = empty
// 3 = locked
// Once an entry it is filled the key never changes, although can be marked as empty
// State transitions: 0 -> 3 -> 1 -> 3 -> 2 -> 3 -> 1 -> ...
static constexpr size_t mask = 3;
static bool is_available(size_t seq_num) {return seq_num == 0;}
static bool is_full(size_t seq_num) {return (seq_num & mask) == 1;}
static bool is_empty(size_t seq_num) {return (seq_num & mask) == 2;}
static bool is_locked(size_t seq_num) {return (seq_num & mask) == 3;}
static size_t add_full(size_t seq_num) {return (seq_num & ~mask) + 5;}
static size_t add_empty(size_t seq_num) {return (seq_num & ~mask) + 6;}
static size_t add_locked(size_t seq_num) {return (seq_num & ~mask) + 7;}

public:
hash_map(long size, Hash&& hash = {}, Equal&& equal = {})
: m(100 + static_cast<long>(1.5 * static_cast<float>(size))),
Expand All @@ -55,65 +72,83 @@ struct hash_map {
index i = first_index(k);
long count = 0;
while (true) {
state status = H[i].status;
if (status == full || status == tomb) {
size_t seq_num = H[i].seq_num;
if (is_full(seq_num) || is_empty(seq_num)) {
if (H[i].key == k) {
if (status == full) return false;
else if (H[i].status.compare_exchange_strong(status, locked)) {
if (is_full(seq_num)) return false; // linearizes on read of seq num
else if (H[i].seq_num.compare_exchange_strong(seq_num, add_locked(seq_num))) {
H[i].value = v;
H[i].status = full;
H[i].seq_num = add_full(seq_num); // linearizes here
return true;
}
} else {
} // else will need to try again, can't linearize if locked
} else { // occupied by a different key
if (count++ == std::min(1000l,m)) {
std::cout << "Hash table overfull" << std::endl;
return false;
}
i = next_index(i);
}
} else if (status == empty) {
if (H[i].status.compare_exchange_strong(status, locked)) {
} else if (is_available(seq_num)) {
if (H[i].seq_num.compare_exchange_strong(seq_num, add_locked(seq_num))) {
H[i].key = k;
H[i].value = v;
H[i].status = full;
H[i].seq_num = add_full(seq_num); // linearizes here
return true;
}
}
// else if locked, try again
}
}

std::optional<V> find(const K& k) {
index i = first_index(k);
while (true) {
if (H[i].status != full) return {};
if (H[i].key == k) return H[i].value;
i = next_index(i);
size_t seq_num = H[i].seq_num;
if (is_available(seq_num)) return {};
if (H[i].key == k) {
// assumes no upserts (i.e. value goes from full to empty to full ..)
// inserts linearize at end of lock, and removes at start of lock,
// so if locked or empty then entry is empty
if (!is_full(seq_num)) return {};
V r = H[i].value;
// ensure value was read atomically
if (seq_num == H[i].seq_num) return r;
// if seq_num increased we know cell was empty at some point between reads
else return {};
} else {
i = next_index(i);
}
}
}

std::optional<V> remove(const K& k) {
index i = first_index(k);
while (true) {
if (H[i].status == empty || H[i].status == locked) return {};
if (H[i].key == k) {
state old = full;
if (H[i].status == full &&
H[i].status.compare_exchange_strong(old, tomb))
return std::move(H[i].value);
else return {};
size_t seq_num = H[i].seq_num;
if (is_available(seq_num)) return {}; // linearizes on read of seq num
else if (is_full(seq_num) || is_empty(seq_num)) {
if (H[i].key == k) {
if (is_empty(seq_num)) return {}; // linearizes on read of seq num
else if (H[i].seq_num.compare_exchange_strong(seq_num, add_locked(seq_num))) {
// linearizes on successful cas, above (i.e. start of lock period)
V r = std::move(H[i].value);
H[i].seq_num = add_empty(seq_num);
return r;
} // can't linearize if locked, try again
} else i = next_index(i); // occupied by different key
}
i = next_index(i);
// else if locked, then try again
}
}

parlay::sequence<K> keys() {
return parlay::map_maybe(H, [] (const entry &x) {
return (x.status == full) ? std::optional{x.key} : std::optional<K>{};});
return is_full(x.seq_num) ? std::optional{x.key} : std::optional<K>{};});
}

size_t size() {
return parlay::reduce(parlay::delayed_map(H, [&] (auto const &x) -> long {
return x.status == full;}));
return is_full(x.seq_num);}));
}
};

0 comments on commit 583624a

Please sign in to comment.