Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 9 additions & 3 deletions src/include/processor/operator/intersect/intersect.h
Original file line number Diff line number Diff line change
Expand Up @@ -61,9 +61,8 @@ class Intersect : public PhysicalOperator {
// Left is always the one with less num of values.
static void twoWayIntersect(common::nodeID_t* leftNodeIDs, common::SelectionVector& lSelVector,
common::nodeID_t* rightNodeIDs, common::SelectionVector& rSelVector);
void intersectLists(const std::vector<common::overflow_value_t>& listsToIntersect);
void populatePayloads(const std::vector<uint8_t*>& tuples,
const std::vector<uint32_t>& listIdxes);
void intersectLists();
void populatePayloads();
bool hasNextTuplesToIntersect();

uint32_t getNumBuilds() { return sharedHTs.size(); }
Expand All @@ -86,6 +85,13 @@ class Intersect : public PhysicalOperator {
uint32_t carryBuildSideIdx;
// Aligned copy of the right-hand list being intersected (see intersectLists).
std::vector<common::nodeID_t> alignedRightList;
// Per-tuple scratch, so probing does not allocate. The vectors hold one entry per build side
// and are sized in initLocalStateInternal; leftSelVector has DEFAULT_VECTOR_CAPACITY.
std::vector<uint8_t*> flatTuplesToIntersect;
std::vector<common::overflow_value_t> listsToIntersect;
std::vector<uint32_t> listIdxes;
std::vector<common::SelectionVector*> selVectorsForIntersectedLists;
common::SelectionVector leftSelVector;
};

} // namespace processor
Expand Down
58 changes: 32 additions & 26 deletions src/processor/operator/intersect/intersect.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -47,10 +47,19 @@ void Intersect::initLocalStateInternal(ResultSet* resultSet, ExecutionContext* /
sharedHT->getHashTable()->getTableSchema()->getColumn(1)->isFlat());
}
alignedRightList.resize(DEFAULT_VECTOR_CAPACITY);
flatTuplesToIntersect.assign(sharedHTs.size(), nullptr);
listsToIntersect.assign(sharedHTs.size(), overflow_value_t{});
listIdxes.assign(sharedHTs.size(), 0);
selVectorsForIntersectedLists.clear();
selVectorsForIntersectedLists.reserve(sharedHTs.size());
for (auto& tuples : probedFlatTuples) {
tuples.clear();
}
std::fill(tupleIdxPerBuildSide.begin(), tupleIdxPerBuildSide.end(), 0);
carryBuildSideIdx = -1u;
}

void Intersect::probeHTs() {
std::vector<std::vector<overflow_value_t>> flatTuples(probeKeyVectors.size());
hash_t hashVal = 0;
for (auto i = 0u; i < probeKeyVectors.size(); i++) {
DASSERT(probeKeyVectors[i]->state->isFlat());
Expand Down Expand Up @@ -85,9 +94,10 @@ void Intersect::twoWayIntersect(nodeID_t* leftNodeIDs, SelectionVector& lSelVect
rSelVector.setToFiltered(outputValuePosition);
}

static std::vector<overflow_value_t> fetchListsToIntersectFromTuples(
const std::vector<uint8_t*>& tuples, const std::vector<bool>& isFlatValue) {
std::vector<overflow_value_t> listsToIntersect(tuples.size());
// Overwrites every entry of listsToIntersect, which has one entry per tuple.
static void fetchListsToIntersectFromTuples(const std::vector<uint8_t*>& tuples,
const std::vector<bool>& isFlatValue, std::vector<overflow_value_t>& listsToIntersect) {
DASSERT(listsToIntersect.size() == tuples.size());
for (auto i = 0u; i < tuples.size(); i++) {
if (isFlatValue[i]) {
listsToIntersect[i] =
Expand All @@ -97,12 +107,12 @@ static std::vector<overflow_value_t> fetchListsToIntersectFromTuples(
memcpy(&listsToIntersect[i], tuples[i] + sizeof(nodeID_t), sizeof(overflow_value_t));
}
}
return listsToIntersect;
}

static std::vector<uint32_t> swapSmallestListToFront(std::vector<overflow_value_t>& lists) {
DASSERT(lists.size() >= 2);
std::vector<uint32_t> listIdxes(lists.size());
// Overwrites every entry of listIdxes, which has one entry per list.
static void swapSmallestListToFront(std::vector<overflow_value_t>& lists,
std::vector<uint32_t>& listIdxes) {
DASSERT(lists.size() >= 2 && listIdxes.size() == lists.size());
iota(listIdxes.begin(), listIdxes.end(), 0);
uint32_t smallestListIdx = 0;
for (auto i = 1u; i < lists.size(); i++) {
Expand All @@ -114,7 +124,6 @@ static std::vector<uint32_t> swapSmallestListToFront(std::vector<overflow_value_
std::swap(lists[smallestListIdx], lists[0]);
std::swap(listIdxes[smallestListIdx], listIdxes[0]);
}
return listIdxes;
}

static void sliceSelVectors(const std::vector<SelectionVector*>& selVectorsToSlice,
Expand All @@ -129,17 +138,16 @@ static void sliceSelVectors(const std::vector<SelectionVector*>& selVectorsToSli
}
}

void Intersect::intersectLists(const std::vector<overflow_value_t>& listsToIntersect) {
void Intersect::intersectLists() {
if (listsToIntersect[0].numElements == 0) {
outKeyVector->state->getSelVectorUnsafe().setSelSize(0);
return;
}
DASSERT(listsToIntersect[0].numElements <= DEFAULT_VECTOR_CAPACITY);
memcpy(outKeyVector->getData(), listsToIntersect[0].value,
listsToIntersect[0].numElements * sizeof(nodeID_t));
SelectionVector lSelVector(listsToIntersect[0].numElements);
lSelVector.setSelSize(listsToIntersect[0].numElements);
std::vector<SelectionVector*> selVectorsForIntersectedLists;
leftSelVector.setToUnfiltered(listsToIntersect[0].numElements);
selVectorsForIntersectedLists.clear();
intersectSelVectors[0]->setToUnfiltered(listsToIntersect[0].numElements);
selVectorsForIntersectedLists.push_back(intersectSelVectors[0].get());
// Lists live in packed factorized-table/unflat storage without alignment padding, so the raw
Expand All @@ -157,24 +165,23 @@ void Intersect::intersectLists(const std::vector<overflow_value_t>& listsToInter
memcpy(alignedRightList.data(), rightList.value,
rightList.numElements * sizeof(nodeID_t));
}
twoWayIntersect((nodeID_t*)outKeyVector->getData(), lSelVector, alignedRightList.data(),
twoWayIntersect((nodeID_t*)outKeyVector->getData(), leftSelVector, alignedRightList.data(),
*intersectSelVectors[i + 1]);
// Here we need to slice all selVectors that have been previously intersected, as all these
// lists need to be selected synchronously to read payloads correctly.
sliceSelVectors(selVectorsForIntersectedLists, lSelVector);
lSelVector.setToUnfiltered();
sliceSelVectors(selVectorsForIntersectedLists, leftSelVector);
leftSelVector.setToUnfiltered();

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The new code reuses one leftSelVector across intersect rounds:

  twoWayIntersect(..., leftSelVector, ...); // shrinks leftSelVector to the survivors
  sliceSelVectors(..., leftSelVector);
  leftSelVector.setToUnfiltered(); // <- keep the shrunken size, just unfilter it for next round

The delta between the two variants of setToUnfiltered() is subtle and easy to miss.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Agreed, the overload hides it. This merged before I got to it, so #1382 passes the size explicitly, leftSelVector.setToUnfiltered(leftSelVector.getSelSize()), with a comment that twoWayIntersect leaves the surviving left IDs at the front of outKeyVector. Resetting the size to the first list's length instead fails acc_hj, cyclic/single_label, cyclic/multi_label and cyclic/large_lists.

selVectorsForIntersectedLists.push_back(intersectSelVectors[i + 1].get());
}
outKeyVector->state->getSelVectorUnsafe().setSelSize(lSelVector.getSelSize());
outKeyVector->state->getSelVectorUnsafe().setSelSize(leftSelVector.getSelSize());
}

void Intersect::populatePayloads(const std::vector<uint8_t*>& tuples,
const std::vector<uint32_t>& listIdxes) {
void Intersect::populatePayloads() {
for (auto i = 0u; i < listIdxes.size(); i++) {
auto listIdx = listIdxes[i];
sharedHTs[listIdx]->getHashTable()->getFactorizedTable()->lookup(
payloadVectorsToScanInto[listIdx], intersectSelVectors[i].get(),
payloadColumnIdxesToScanFrom[listIdx], tuples[listIdx]);
payloadColumnIdxesToScanFrom[listIdx], flatTuplesToIntersect[listIdx]);
}
}

Expand Down Expand Up @@ -218,16 +225,15 @@ bool Intersect::getNextTuplesInternal(ExecutionContext* context) {
// too large to fit in a single ValueVector, we end up chunking the list as multiple tuples
// in FTable. Thus, when performing the intersection, we need to perform cartesian product
// between all flat tuples probed from all build sides.
std::vector<uint8_t*> flatTuplesToIntersect(getNumBuilds());
for (auto i = 0u; i < getNumBuilds(); i++) {
flatTuplesToIntersect[i] = probedFlatTuples[i][tupleIdxPerBuildSide[i]];
}
auto listsToIntersect =
fetchListsToIntersectFromTuples(flatTuplesToIntersect, isIntersectListAFlatValue);
auto listIdxes = swapSmallestListToFront(listsToIntersect);
intersectLists(listsToIntersect);
fetchListsToIntersectFromTuples(flatTuplesToIntersect, isIntersectListAFlatValue,
listsToIntersect);
swapSmallestListToFront(listsToIntersect, listIdxes);
intersectLists();
if (outKeyVector->state->getSelVector().getSelSize() != 0) {
populatePayloads(flatTuplesToIntersect, listIdxes);
populatePayloads();
}
if (!hasNextTuplesToIntersect()) {
carryBuildSideIdx = -1u;
Expand Down
38 changes: 38 additions & 0 deletions test/test_files/cyclic/large_lists.test
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
-DATASET CSV empty

--

-CASE IntersectListsLongerThanOneVector
-STATEMENT CREATE NODE TABLE N(id INT64 PRIMARY KEY);
---- ok
-STATEMENT CREATE REL TABLE E(FROM N TO N);
---- ok
-STATEMENT UNWIND range(0, 3000) AS i CREATE (:N {id: i});
---- ok
# Node 0 points at the other 3000 nodes, so its list is split over two 2048-entry build tuples
# and a single probe intersects several list pairs.
-STATEMENT MATCH (h:N {id: 0}), (n:N) WHERE n.id > 0 CREATE (h)-[:E]->(n);
---- ok
-STATEMENT MATCH (a:N), (b:N) WHERE a.id > 0 AND b.id = a.id + 1 CREATE (a)-[:E]->(b);
---- ok
-STATEMENT MATCH (a:N), (b:N) WHERE a.id > 0 AND b.id = a.id + 2 CREATE (a)-[:E]->(b);
---- ok
-LOG Triangle
-STATEMENT MATCH (a:N)-[e1:E]->(b:N)-[e2:E]->(c:N), (a)-[e3:E]->(c)
HINT (((a JOIN e1) JOIN b) MULTI_JOIN e2 MULTI_JOIN e3) JOIN c
RETURN COUNT(*);
---- 1
8995
-STATEMENT MATCH (a:N)-[e1:E]->(b:N)-[e2:E]->(c:N), (a)-[e3:E]->(c)
HINT (((a JOIN e1) JOIN b) MULTI_JOIN e2 MULTI_JOIN e3) JOIN c
RETURN a.id, b.id, c.id ORDER BY c.id DESC, b.id DESC LIMIT 3;
---- 3
0|2999|3000
2998|2999|3000
0|2998|3000
-LOG FourClique
-STATEMENT MATCH (a:N)-[e1:E]->(b:N)-[e2:E]->(c:N), (a)-[e3:E]->(c), (a)-[e4:E]->(d:N), (b)-[e5:E]->(d), (c)-[e6:E]->(d)
HINT (((((a JOIN e1) JOIN b) MULTI_JOIN e2 MULTI_JOIN e3) JOIN c) MULTI_JOIN e4 MULTI_JOIN e5 MULTI_JOIN e6) JOIN d
RETURN COUNT(*), SUM(a.id + b.id * 3 + c.id * 5 + d.id * 7);
---- 1
2998|67489477
Loading