Skip to content
Open
75 changes: 19 additions & 56 deletions include/dbscan/algo.h
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,7 @@ int DBSCAN(intT n, floatT* PF, double epsilon, intT minPts, bool* coreFlagOut, i
intT count = 0;
auto isCore = [&] (pointT *p) {
if(count >= minPts) return true;
if(p->distSqr(P[i]) <= epsSqr) {//todo sqrt opt
if(p->distSqr(P[i]) <= epsSqr) {
count ++;}
return false;};
G->nghPointMap(P[i].coordinate(), isCore);
Expand Down Expand Up @@ -165,67 +165,30 @@ int DBSCAN(intT n, floatT* PF, double epsilon, intT minPts, bool* coreFlagOut, i
delete G;

//improving cluster representation
auto cluster2 = newA(intT, n);
auto flag = newA(intT, n+1);
parallel_for(0, n, [&](intT i){cluster2[i] = cluster[i];});
sampleSort(cluster, n, std::less<intT>());

flag[0] = 1;
parallel_for(1, n, [&](intT i){
if (cluster[i] != cluster[i-1])
flag[i] = 1;
else
flag[i] = 0;
});
flag[n] = sequence::prefixSum(flag, 0, n);

// typedef pair<intT,intT> eType;
struct myPair {
intT first;
intT second;
myPair(intT _first, intT _second): first(_first), second(_second) {}
myPair(): first(-1), second(-1) {}
inline bool operator==(myPair a) {
if(a.first==first && a.second== second)
return true;
else
return false;
}
};

typedef Table<hashSimplePair<myPair>,intT> tableT;
auto T = new tableT(n, hashSimplePair<myPair>());
parallel_for(0, n, [&] (intT i) {
if (flag[i] != flag[i+1]) {
// T->insert(make_pair(cluster[i], flag[i]));
T->insert(myPair(cluster[i], flag[i]));
}
});
// O(n) renumbering: cluster IDs are point indices ∈ [0,n), use prefix sum
// instead of O(n log n) sort + hash table.
auto idMap = newA(intT, n);
parallel_for(0, n, [&](intT i) { idMap[i] = 0; });
parallel_for(0, n, [&](intT i) {
if (cluster[i] >= 0) idMap[cluster[i]] = 1;
});
sequence::prefixSum(idMap, 0, n); // exclusive: idMap[cid] = sequential ID for cid

if(T->find(-1).second < 0) {
parallel_for(0, n, [&](intT i){
cluster2[i] = T->find(cluster2[i]).second;
});
} else {
parallel_for(0, n, [&](intT i){
if (cluster2[i] > 0)
cluster2[i] = T->find(cluster2[i]).second-1;
});
}
// Remap to sequential IDs (separate buffer to avoid read/write conflict)
auto cluster2 = newA(intT, n);
parallel_for(0, n, [&](intT i) {
cluster2[i] = (cluster[i] >= 0) ? idMap[cluster[i]] : cluster[i];
});

//restoring order
parallel_for(0, n, [&](intT i){
cluster[I[i]] = cluster2[i];
});
parallel_for(0, n, [&](intT i){
coreFlagOut[I[i]] = coreFlag[i];
});
parallel_for(0, n, [&](intT i) {
cluster[I[i]] = cluster2[i];
coreFlagOut[I[i]] = coreFlag[i];
});

free(I);
free(cluster2);
free(flag);
T->del(); // Required to clean-up T's internals
delete T;
free(idMap);
free(P);
#ifdef VERBOSE
cout << "output-time = " << tt.stop() << endl;
Expand Down
4 changes: 4 additions & 0 deletions include/dbscan/cell.h
Original file line number Diff line number Diff line change
Expand Up @@ -172,7 +172,11 @@ struct cellHash {
bool replaceQ(eType c1, eType c2) {return 0;}

bool cas(eType* p, eType o, eType n) {
#ifdef _MSC_VER
return std::atomic_compare_exchange_strong_explicit(
reinterpret_cast<std::atomic<eType>*>(p), &o, n, std::memory_order_acq_rel, std::memory_order_acquire);
#else
return __atomic_compare_exchange(p, &o, &n, false, __ATOMIC_ACQ_REL, __ATOMIC_ACQUIRE);
#endif
}
};
32 changes: 17 additions & 15 deletions include/dbscan/coreBccp.h
Original file line number Diff line number Diff line change
Expand Up @@ -29,17 +29,18 @@
#include "pbbs/parallel.h"
#include "pbbs/utils.h"

// r holds squared distance; using distSqr and nodeDistanceSqr avoids sqrt in hot path
template<class nodeT, class objT>
inline void compBcpCoreHSerial(nodeT* n1, nodeT* n2, floatT* r, intT* coreFlag, objT* P) {
if (n1->nodeDistance(n2) > *r) return;
if (n1->nodeDistanceSqr(n2) > *r) return;

if (n1->isLeaf() && n2->isLeaf()) {//basecase
for (intT i=0; i<n1->size(); ++i) {
for (intT j=0; j<n2->size(); ++j) {
auto pi = n1->getItem(i);
auto pj = n2->getItem(j);
if (coreFlag[pi - P] && coreFlag[pj - P]) {
floatT dist = pi->dist(*pj);
floatT dist = pi->distSqr(*pj);
r[0] = min(r[0], dist);
}
}
Expand Down Expand Up @@ -78,30 +79,31 @@ inline void compBcpCoreHSerial(nodeT* n1, nodeT* n2, floatT* r, intT* coreFlag,

template<class nodeT, class objT>
inline void compBcpCoreHBase(nodeT* n1, nodeT* n2, floatT* r, intT* coreFlag, objT* P) {
if (n1->nodeDistance(n2) > *r) return;
if (n1->nodeDistanceSqr(n2) > *r) return;

if (n1->isLeaf() && n2->isLeaf()) {//basecase
for (intT i=0; i<n1->size(); ++i) {
for (intT j=0; j<n2->size(); ++j) {
auto pi = n1->getItem(i);
auto pj = n2->getItem(j);
if (coreFlag[pi - P] && coreFlag[pj - P]) {
floatT dist = pi->dist(*pj);
floatT dist = pi->distSqr(*pj);
utils::writeMin(r, dist);
}
}
}
} else {//recursive, todo consider call order, might help
} else {//recursive
if (n1->isLeaf()) {
if (n1->nodeDistance(n2->L()) < n1->nodeDistance(n2->R())) {
// nodeDistanceSqr avoids sqrt; monotonicity preserves ordering
if (n1->nodeDistanceSqr(n2->L()) < n1->nodeDistanceSqr(n2->R())) {
compBcpCoreH(n1, n2->L(), r, coreFlag, P);
compBcpCoreH(n1, n2->R(), r, coreFlag, P);
} else {
compBcpCoreH(n1, n2->R(), r, coreFlag, P);
compBcpCoreH(n1, n2->L(), r, coreFlag, P);
}
} else if (n2->isLeaf()) {
if (n2->nodeDistance(n1->L()) < n2->nodeDistance(n1->R())) {
if (n2->nodeDistanceSqr(n1->L()) < n2->nodeDistanceSqr(n1->R())) {
compBcpCoreH(n2, n1->L(), r, coreFlag, P);
compBcpCoreH(n2, n1->R(), r, coreFlag, P);
} else {
Expand All @@ -115,7 +117,7 @@ inline void compBcpCoreHBase(nodeT* n1, nodeT* n2, floatT* r, intT* coreFlag, ob
ordering[2] = make_pair(n2->L(), n1->R());
ordering[3] = make_pair(n2->R(), n1->R());
auto bbd = [&](pair<nodeT*,nodeT*> p1, pair<nodeT*,nodeT*> p2) {
return p1.first->nodeDistance(p1.second) < p2.first->nodeDistance(p2.second);};
return p1.first->nodeDistanceSqr(p1.second) < p2.first->nodeDistanceSqr(p2.second);};
quickSortSerial(ordering, 4, bbd);
for (intT o=0; o<4; ++o) {
compBcpCoreH(ordering[o].first, ordering[o].second, r, coreFlag, P);}
Expand All @@ -125,21 +127,21 @@ inline void compBcpCoreHBase(nodeT* n1, nodeT* n2, floatT* r, intT* coreFlag, ob

template<class nodeT, class objT>
inline void compBcpCoreH(nodeT* n1, nodeT* n2, floatT* r, intT* coreFlag, objT* P) {
if (n1->nodeDistance(n2) > *r) return;
if (n1->nodeDistanceSqr(n2) > *r) return;

if ((n1->isLeaf() && n2->isLeaf()) || (n1->size()+n2->size() < 2000)) {
return compBcpCoreHBase(n1, n2, r, coreFlag, P);
} else {//recursive, todo consider call order, might help
} else {//recursive
if (n1->isLeaf()) {
if (n1->nodeDistance(n2->L()) < n1->nodeDistance(n2->R())) {
if (n1->nodeDistanceSqr(n2->L()) < n1->nodeDistanceSqr(n2->R())) {
par_do([&](){compBcpCoreH(n1, n2->L(), r, coreFlag, P);},
[&](){compBcpCoreH(n1, n2->R(), r, coreFlag, P);});
} else {
par_do([&](){compBcpCoreH(n1, n2->R(), r, coreFlag, P);},
[&](){compBcpCoreH(n1, n2->L(), r, coreFlag, P);});
}
} else if (n2->isLeaf()) {
if (n2->nodeDistance(n1->L()) < n2->nodeDistance(n1->R())) {
if (n2->nodeDistanceSqr(n1->L()) < n2->nodeDistanceSqr(n1->R())) {
par_do([&](){compBcpCoreH(n2, n1->L(), r, coreFlag, P);},
[&](){compBcpCoreH(n2, n1->R(), r, coreFlag, P);});
} else {
Expand All @@ -153,7 +155,7 @@ inline void compBcpCoreH(nodeT* n1, nodeT* n2, floatT* r, intT* coreFlag, objT*
ordering[2] = make_pair(n2->L(), n1->R());
ordering[3] = make_pair(n2->R(), n1->R());
auto bbd = [&](pair<nodeT*,nodeT*> p1, pair<nodeT*,nodeT*> p2) {
return p1.first->nodeDistance(p1.second) < p2.first->nodeDistance(p2.second);};
return p1.first->nodeDistanceSqr(p1.second) < p2.first->nodeDistanceSqr(p2.second);};
quickSortSerial(ordering, 4, bbd);
parallel_for (0, 4, [&](intT o) {
compBcpCoreH(ordering[o].first, ordering[o].second, r, coreFlag, P);}, 1);
Expand All @@ -179,11 +181,11 @@ inline bool hasEdge(intT n1, intT n2, intT* coreFlag, objT* P, floatT epsilon, c

if (!trees[n1])
trees[n1] = new treeT(cells[n1].getItem(), cells[n1].size(), false);//todo allocation, parallel
if (!trees[n2])
if (!trees[n2])
trees[n2] = new treeT(cells[n2].getItem(), cells[n2].size(), false);//todo allocation, parallel
floatT r = floatMax();
compBcpCoreH(trees[n1]->rootNode(), trees[n2]->rootNode(), &r, coreFlag, P);
return r <= epsilon;
return r <= epsilon * epsilon; // r holds squared distance now
}

#endif
90 changes: 62 additions & 28 deletions include/dbscan/grid.h
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@

#pragma once

#include <atomic>
#include <mutex>
#include "cell.h"
#include "point.h"
Expand Down Expand Up @@ -77,7 +78,7 @@ struct grid {
tableT* table=NULL;
treeT* tree=NULL;
intT totalPoints;
cellBuf **nbrCache;
std::atomic<cellBuf*>* nbrCache;
std::mutex* cacheLocks;

/**
Expand All @@ -90,26 +91,29 @@ struct grid {
r(rr), pMin(pMinn), cellCapacity(cellMax), totalPoints(0) {

cells = newA(cellT, cellCapacity);
nbrCache = newA(cellBuf*, cellCapacity);
nbrCache = new std::atomic<cellBuf*>[cellCapacity];
cacheLocks = (std::mutex*) malloc(cellCapacity * sizeof(std::mutex));
parallel_for(0, cellCapacity, [&](intT i) {
new (&cacheLocks[i]) std::mutex();
nbrCache[i] = NULL;
cells[i].init();
});
// cacheLocks/nbrCache/cells are initialized lazily in insertParallel over
// numCells only, so nothing to initialize here.
numCells = 0;

myHash = new cellHashT(pMinn, r);
table = new tableT(cellMax*2, cellHash<dim, objT>(myHash));//todo load
// Hash table sized for expected number of cells, with safety rebuild
// in insertParallel if numCells exceeds the estimate.
intT tableHint = std::max((intT)2048, cellMax / 4);
table = new tableT(tableHint, cellHash<dim, objT>(myHash));
}

~grid() {
free(cells);
free(cacheLocks);
parallel_for(0, cellCapacity, [&](intT i) {
if(nbrCache[i]) delete nbrCache[i];
// Only [0, numCells) were initialized in insertParallel; reading beyond
// that would touch uninitialized atomics.
parallel_for(0, numCells, [&](intT i) {
auto cached = nbrCache[i].load(std::memory_order_relaxed);
if(cached) delete cached;
});
free(nbrCache);
delete[] nbrCache;
if(myHash) delete myHash;
if(table) {
table->del();
Expand Down Expand Up @@ -147,22 +151,24 @@ struct grid {
}
return false;};//todo, optimize
int idx = bait - cells;
if (nbrCache[idx]) {
auto accum = nbrCache[idx];
for (auto accum_i : *accum) {
// Acquire ensures vector contents are visible if pointer is non-null
auto cached = nbrCache[idx].load(std::memory_order_acquire);
if (cached) {
for (auto accum_i : *cached) {
if(fWrap(accum_i)) break;
}
} else {
// wait for other threads to do their thing then try again
std::lock_guard<std::mutex> lock(cacheLocks[idx]);
if (nbrCache[idx]) {
auto accum = nbrCache[idx];
for (auto accum_i : *accum) {
cached = nbrCache[idx].load(std::memory_order_relaxed);
if (cached) {
for (auto accum_i : *cached) {
if (fWrap(accum_i)) break;
}
} else {
floatT hop = sqrt(dim + 3) * 1.0000001;
nbrCache[idx] = tree->rangeNeighbor(bait, r * hop, fStop, fWrap, true, nbrCache[idx]);
auto result = tree->rangeNeighbor(bait, r * hop, fStop, fWrap, true, (cellBuf*)nullptr);
// Release ensures vector contents are fully written before pointer is visible
nbrCache[idx].store(result, std::memory_order_release);
}
}
}
Expand All @@ -176,22 +182,22 @@ struct grid {
return false;
};
int idx = bait - cells;
if (nbrCache[idx]) {
auto accum = nbrCache[idx];
for (auto accum_i : *accum) {
auto cached = nbrCache[idx].load(std::memory_order_acquire);
if (cached) {
for (auto accum_i : *cached) {
if (fWrap(accum_i)) break;
}
} else {
// wait for other threads to do their thing then try again
std::lock_guard<std::mutex> lock(cacheLocks[idx]);
if (nbrCache[idx]) {
auto accum = nbrCache[idx];
for (auto accum_i : *accum) {
cached = nbrCache[idx].load(std::memory_order_relaxed);
if (cached) {
for (auto accum_i : *cached) {
if (fWrap(accum_i)) break;
}
} else {
floatT hop = sqrt(dim + 3) * 1.0000001;
nbrCache[bait-cells] = tree->rangeNeighbor(bait, r * hop, fStop, fWrap, true, nbrCache[idx]);
auto result = tree->rangeNeighbor(bait, r * hop, fStop, fWrap, true, (cellBuf*)nullptr);
nbrCache[idx].store(result, std::memory_order_release);
}
}
}
Expand All @@ -205,9 +211,24 @@ struct grid {
freeFlag=true;}

parallel_for(0, nn, [&](intT i){I[i] = i;});

// Pre-compute integer cell coordinates to avoid floor() in every sort comparison
auto cellKeys = newA(intT, nn * dim);
floatT invR = 1.0 / r;
parallel_for(0, nn, [&](intT i) {
for (int d = 0; d < dim; d++) {
cellKeys[i * dim + d] = (intT)floor((P[i][d] - pMin[d]) * invR);
}
});
auto ipLess = [&] (intT a, intT b) {
return pointGridCmp<dim, objT, geoPointT>(P[a], P[b], pMin, r);};
for (int d = 0; d < dim; d++) {
intT ca = cellKeys[a * dim + d];
intT cb = cellKeys[b * dim + d];
if (ca != cb) return ca < cb;
}
return false;};
sampleSort(I, nn, ipLess);
free(cellKeys);
parallel_for(0, nn, [&](intT i){PP[i] = P[I[i]];});

flag[0] = 1;
Expand All @@ -224,6 +245,19 @@ struct grid {
if (numCells > cellCapacity) {
cout << "error, grid insert exceeded cell capacity, abort()" << endl;abort();}

// Rebuild hash table if initial estimate was too small
if (numCells > cellCapacity / 8) {
table->del(); delete table;
table = new tableT(numCells * 2, cellHash<dim, objT>(myHash));
}

// Initialize only the cells that will actually be used
parallel_for(0, numCells, [&](intT i) {
new (&cacheLocks[i]) std::mutex();
nbrCache[i].store(nullptr, std::memory_order_relaxed);
cells[i].init();
});

parallel_for(0, nn, [&](intT i) {
if (flag[i] != flag[i+1]) {
auto c = &cells[flag[i]];
Expand Down
Loading
Loading