Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
31 commits
Select commit Hold shift + click to select a range
5d356d7
Added code for lil implementation. Currently, it is not functioning c…
NoahP-K Jun 20, 2025
e4958d0
Continued debugging, still not fully functional. Handles inserts from…
NoahP-K Jun 25, 2025
dbbccd3
The code still does not work. Just committing to back up the current …
NoahP-K Jun 27, 2025
d01e1ce
Updated insert to determine depth from the sum of the prefix length o…
NoahP-K Jun 30, 2025
e5665f1
Just some tweaking from the previous version. It currently is facing …
NoahP-K Jul 2, 2025
0fe2232
Committing before pulling from main
NoahP-K Jul 10, 2025
4454c3e
Merge branch 'main' of github.com:BU-DiSC/Qu-ART into noahpk/implemen…
NoahP-K Jul 10, 2025
d0f4997
Updated gitignore
NoahP-K Jul 10, 2025
9df268e
..
NoahP-K Jul 10, 2025
3de004d
fixed conflicts from earlier merge from main
ramananeesh Jul 11, 2025
36575ff
removed commented code
ramananeesh Jul 11, 2025
9cce11e
cleared warnings
ramananeesh Jul 11, 2025
37258e9
Started writing documentation file for lil insertion.
NoahP-K Jul 11, 2025
0a585a8
Wrote file to document lil insertion
NoahP-K Jul 14, 2025
df1eaa5
lil_insert.md contents have been moved to the wiki and the original f…
NoahP-K Jul 14, 2025
f657eea
Implemented new version of lil. Not yet functional.
NoahP-K Jul 15, 2025
50e44b8
Recent changes
NoahP-K Jul 17, 2025
3dde821
New changes
NoahP-K Jul 21, 2025
d6acd23
Unclear if the insert function works. The tree printing method shows …
NoahP-K Jul 22, 2025
7fd4f59
More debugging. The code passes basic tests, but will still need to t…
NoahP-K Jul 22, 2025
9c2d256
Re-committing without large workload file included.
NoahP-K Jul 24, 2025
96edd57
Merge branch 'main' of github.com:BU-DiSC/Qu-ART into noahpk/implemen…
NoahP-K Jul 24, 2025
c428fb0
Update main.cpp
NoahP-K Jul 25, 2025
4cf501d
Update main.cpp
NoahP-K Jul 25, 2025
e7a54f3
Update insert_profiling.cpp
NoahP-K Jul 25, 2025
a564c4d
Update QuART_lil.h
NoahP-K Jul 25, 2025
83294d3
Update Helper.h
NoahP-K Jul 25, 2025
051a23d
Fixed code according to PR comments.
NoahP-K Jul 26, 2025
52f7e0d
Merge branch 'noahpk/implement-lil-optimization-in-art' of github.com…
NoahP-K Jul 26, 2025
5e54c62
Minor change to deal with compilation error.
NoahP-K Jul 28, 2025
fc6649f
Fixed according to PR comments.
NoahP-K Jul 29, 2025
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
5 changes: 4 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
@@ -1,4 +1,7 @@
**/*.csv
build/
.DS_Store
results/
Comment thread
NoahP-K marked this conversation as resolved.
.vscode/launch.json
.vscode/tasks.json
results/
workloads/
53 changes: 31 additions & 22 deletions ART.h
Original file line number Diff line number Diff line change
Expand Up @@ -37,10 +37,12 @@ class ART {
ArtNode* root; // pointer to root node of tree
ArtNode* fp; // pointer to fast path node
std::array<ArtNode*, maxPrefixLength> fp_path; // path that leads to fp
size_t fp_path_length; // stores length of fp path
ArtNode* fp_leaf; // pointer to leaf node in fast path
size_t fp_depth; // depth that will be used during fp insertion
ArtNode** fp_ref; // reference to fp node, used for insertion
std::array<ArtNode**, maxPrefixLength>
fp_path_ref; // references to nodes on fp_path
size_t fp_path_length; // stores length of fp path
ArtNode* fp_leaf; // pointer to leaf node in fast path
size_t fp_depth; // depth that will be used during fp insertion
ArtNode** fp_ref; // reference to fp node, used for insertion

// constructor
ART()
Expand All @@ -57,20 +59,20 @@ class ART {
}

ArtNode* lookup(uint8_t key[]) {
return lookup(this, root, key, maxPrefixLength, 0, maxPrefixLength);
return lookup(root, key, maxPrefixLength, 0, maxPrefixLength);
}

Chain* rangelookup(uint8_t l_key[], unsigned l_keyLength, uint8_t h_key[],
uint8_t h_keyLength, unsigned depth,
unsigned maxKeyLength) {
return rangelookup(this, root, l_key, l_keyLength, h_key, h_keyLength,
depth, maxKeyLength);
uint8_t h_keyLength, unsigned maxKeyLength) {
return rangelookup(root, l_key, l_keyLength, h_key, h_keyLength,
maxKeyLength);
}

void printTree() { printTree(this->root, 0); }

// Method to verify the tail path after each insertion
// Returns true if the fast path (fp_path) leads to the correct fp and fp_leaf
// Returns true if the fast path (fp_path) leads to the correct fp and
// fp_leaf
bool verifyTailPath() {
if (this->fp_path_length == 0) {
// No fast path to verify
Expand All @@ -80,7 +82,8 @@ class ART {
ArtNode* current = this->root;
// Traverse the tree following the fp_path
for (size_t i = 0; i < this->fp_path_length; i++) {
// If we're at the last node in the fp_path, check if it's the fp node
// If we're at the last node in the fp_path, check if it's the fp
Comment thread
NoahP-K marked this conversation as resolved.
// node
if (i == this->fp_path_length - 1) {
if (current == this->fp) {
// Check if the leaf value matches the expected fp_leaf
Expand All @@ -90,13 +93,16 @@ class ART {
} else {
std::cerr << "Error: fp_leaf mismatch. Expected "
<< getLeafValue(maximum(current)) << ", got "
<< getLeafValue(this->fp_leaf) << "." << std::endl;
<< getLeafValue(this->fp_leaf) << "."
<< std::endl;
return false;
}
} else {
std::cerr << "Error: last node in fp_path is not the fp. Expected "
std::cerr << "Error: last node in fp_path is not the fp. "
"Expected "
<< static_cast<void*>(current) << ", got "
<< static_cast<void*>(this->fp) << "." << std::endl;
<< static_cast<void*>(this->fp) << "."
<< std::endl;
return false;
}
}
Expand All @@ -109,7 +115,8 @@ class ART {
// Move to the last child (rightmost)
current = node->child[node->count - 1];
} else {
std::cerr << "Error: NodeType4 has no children." << std::endl;
std::cerr << "Error: NodeType4 has no children."
<< std::endl;
return false;
}
break;
Expand All @@ -120,7 +127,8 @@ class ART {
// Move to the last child (rightmost)
current = node->child[node->count - 1];
} else {
std::cerr << "Error: NodeType16 has no children." << std::endl;
std::cerr << "Error: NodeType16 has no children."
<< std::endl;
return false;
}
break;
Expand All @@ -134,7 +142,8 @@ class ART {
if (node->childIndex[pos] != emptyMarker) {
current = node->child[node->childIndex[pos]];
} else {
std::cerr << "Error: NodeType48 has no valid children." << std::endl;
std::cerr << "Error: NodeType48 has no valid children."
<< std::endl;
return false;
}
break;
Expand All @@ -147,7 +156,8 @@ class ART {
if (node->child[pos]) {
current = node->child[pos];
} else {
std::cerr << "Error: NodeType256 has no valid children." << std::endl;
std::cerr << "Error: NodeType256 has no valid children."
<< std::endl;
return false;
}
break;
Expand Down Expand Up @@ -260,7 +270,7 @@ class ART {
}

// Lookup function, returns ArtNode
ArtNode* lookup(ART* tree, ArtNode* node, uint8_t key[], unsigned keyLength,
ArtNode* lookup(ArtNode* node, uint8_t key[], unsigned keyLength,
unsigned depth, unsigned maxKeyLength) {
// Find the node with a matching key, optimistic version

Expand Down Expand Up @@ -349,9 +359,8 @@ class ART {
}

// Range lookup function, returns a Chain of ArtNode
Chain* rangelookup(ART* tree, ArtNode* node, uint8_t l_key[],
unsigned l_keyLength, uint8_t h_key[],
uint8_t h_keyLength, unsigned depth,
Chain* rangelookup(ArtNode* node, uint8_t l_key[], unsigned l_keyLength,
uint8_t h_key[], uint8_t h_keyLength,
unsigned maxKeyLength) {
// Find the node with a matching key, optimistic version
Chain* queue =
Expand Down
34 changes: 25 additions & 9 deletions ArtNode.h
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,9 @@ struct Node4 : ArtNode {
memset(child, 0, sizeof(child));
}

void lilInsertNode4(ART* tree, ArtNode** nodeRef, uint8_t keyByte,
ArtNode* child);

// Base ART insert function for Node4
void insertNode4(ART* tree, ArtNode** nodeRef, uint8_t keyByte,
ArtNode* child);
Expand All @@ -92,6 +95,9 @@ struct Node16 : ArtNode {
memset(child, 0, sizeof(child));
}

void lilInsertNode16(ART* tree, ArtNode** nodeRef, uint8_t keyByte,
ArtNode* child);

// Base ART insert function for Node16
void insertNode16(ART* tree, ArtNode** nodeRef, uint8_t keyByte,
ArtNode* child);
Expand All @@ -115,6 +121,9 @@ struct Node48 : ArtNode {
memset(child, 0, sizeof(child));
}

void lilInsertNode48(ART* tree, ArtNode** nodeRef, uint8_t keyByte,
ArtNode* child);

// Base ART insert function for Node48
void insertNode48(ART* tree, ArtNode** nodeRef, uint8_t keyByte,
ArtNode* child);
Expand All @@ -134,6 +143,10 @@ struct Node256 : ArtNode {

Node256() : ArtNode(NodeType256) { memset(child, 0, sizeof(child)); }

void lilInsertNode256(ART* tree [[maybe_unused]],
ArtNode** nodeRef [[maybe_unused]], uint8_t keyByte,
ArtNode* child);

// Base ART insert function for Node256
void insertNode256(ART* tree, ArtNode** nodeRef, uint8_t keyByte,
ArtNode* child);
Expand Down Expand Up @@ -175,8 +188,7 @@ void Node4::insertNode4(ART* tree, ArtNode** nodeRef, uint8_t keyByte,
if (this->count < 4) {
// Insert element
unsigned pos;
for (pos = 0; (pos < this->count) && (this->key[pos] < keyByte); pos++)
;
for (pos = 0; (pos < this->count) && (this->key[pos] < keyByte); pos++);
// Shift keys and children to the right to make space for the new
// key/child. This preserves the sorted order of keys in the node.
memmove(this->key + pos + 1, this->key + pos, this->count - pos);
Expand All @@ -199,7 +211,8 @@ void Node4::insertNode4(ART* tree, ArtNode** nodeRef, uint8_t keyByte,
}
}

void Node4::eraseNode4(ART* tree, ArtNode** nodeRef, ArtNode** leafPlace) {
void Node4::eraseNode4(ART* tree [[maybe_unused]], ArtNode** nodeRef,
ArtNode** leafPlace) {
// Delete leaf from inner node
unsigned pos = leafPlace - this->child;
// Shift keys and children to the left to fill the gap left by the removed
Expand Down Expand Up @@ -282,7 +295,8 @@ void Node16::insertNode16(ART* tree, ArtNode** nodeRef, uint8_t keyByte,
}
}

void Node16::eraseNode16(ART* tree, ArtNode** nodeRef, ArtNode** leafPlace) {
void Node16::eraseNode16(ART* tree [[maybe_unused]], ArtNode** nodeRef,
ArtNode** leafPlace) {
// Delete leaf from inner node
unsigned pos = leafPlace - this->child;
// Shift keys and children to the left to fill the gap left by the removed
Expand Down Expand Up @@ -312,8 +326,7 @@ void Node48::insertNode48(ART* tree, ArtNode** nodeRef, uint8_t keyByte,
// Insert element
unsigned pos = this->count;
if (this->child[pos])
for (pos = 0; this->child[pos] != NULL; pos++)
;
for (pos = 0; this->child[pos] != NULL; pos++);
// No memmove needed here because Node48 uses a mapping (childIndex) and
// a dense array.
this->child[pos] = child;
Expand All @@ -333,7 +346,8 @@ void Node48::insertNode48(ART* tree, ArtNode** nodeRef, uint8_t keyByte,
}
}

void Node48::eraseNode48(ART* tree, ArtNode** nodeRef, uint8_t keyByte) {
void Node48::eraseNode48(ART* tree [[maybe_unused]], ArtNode** nodeRef,
uint8_t keyByte) {
// Delete leaf from inner node
// No memmove needed here because Node48 uses a mapping (childIndex) and a
// dense array.
Expand All @@ -358,7 +372,8 @@ void Node48::eraseNode48(ART* tree, ArtNode** nodeRef, uint8_t keyByte) {
}
}

void Node256::insertNode256(ART* tree, ArtNode** nodeRef, uint8_t keyByte,
void Node256::insertNode256(ART* tree [[maybe_unused]],
ArtNode** nodeRef [[maybe_unused]], uint8_t keyByte,
ArtNode* child) {
// Insert leaf into inner node
// No memmove needed here because Node256 uses a direct mapping for all
Expand All @@ -367,7 +382,8 @@ void Node256::insertNode256(ART* tree, ArtNode** nodeRef, uint8_t keyByte,
this->child[keyByte] = child;
}

void Node256::eraseNode256(ART* tree, ArtNode** nodeRef, uint8_t keyByte) {
void Node256::eraseNode256(ART* tree [[maybe_unused]], ArtNode** nodeRef,
uint8_t keyByte) {
// Delete leaf from inner node
// No memmove needed here because Node256 uses a direct mapping for all
// possible keys.
Expand Down
118 changes: 114 additions & 4 deletions ArtNodeNewMethods.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,7 @@ void Node4::tailInsertNode4(ART* tree, ArtNode** nodeRef, uint8_t keyByte,
if (this->count < 4) {
// Insert element
unsigned pos;
for (pos = 0; (pos < this->count) && (this->key[pos] < keyByte); pos++)
;
for (pos = 0; (pos < this->count) && (this->key[pos] < keyByte); pos++);
// Shift keys and children to the right to make space for the new
// key/child. This preserves the sorted order of keys in the node.
memmove(this->key + pos + 1, this->key + pos, this->count - pos);
Expand Down Expand Up @@ -196,8 +195,7 @@ void Node48::tailInsertNode48(
// Insert element
unsigned pos = this->count;
if (this->child[pos])
for (pos = 0; this->child[pos] != NULL; pos++)
;
for (pos = 0; this->child[pos] != NULL; pos++);
// No memmove needed here because Node48 uses a mapping (childIndex) and
// a dense array. The childIndex array maps key bytes to positions in
// the child array, so insertion does not require shifting elements.
Expand Down Expand Up @@ -292,4 +290,116 @@ void Node256::tailInsertNode256(
}
}
}

void Node4::lilInsertNode4(ART* tree, ArtNode** nodeRef, uint8_t keyByte,
ArtNode* child) {
// Insert leaf into inner node
if (this->count < 4) {
// Insert element
unsigned pos;
for (pos = 0; (pos < this->count) && (this->key[pos] < keyByte); pos++);
memmove(this->key + pos + 1, this->key + pos, this->count - pos);
memmove(this->child + pos + 1, this->child + pos,
(this->count - pos) * sizeof(uintptr_t));
this->key[pos] = keyByte;
this->child[pos] = child;
this->count++;
} else {
// Grow to Node16
Node16* newNode = new Node16();
*nodeRef = newNode;

// update fast path
tree->fp = newNode;
tree->fp_path[tree->fp_path_length - 1] = newNode;
tree->fp_path_ref[tree->fp_path_length - 1] = nodeRef;

newNode->count = 4;
copyPrefix(this, newNode);
for (unsigned i = 0; i < 4; i++)
newNode->key[i] = flipSign(this->key[i]);
memcpy(newNode->child, this->child, this->count * sizeof(uintptr_t));
delete this;
return newNode->lilInsertNode16(tree, nodeRef, keyByte, child);
}
}

void Node16::lilInsertNode16(ART* tree, ArtNode** nodeRef, uint8_t keyByte,
ArtNode* child) {
// Insert leaf into inner node
if (this->count < 16) {
// Insert element
uint8_t keyByteFlipped = flipSign(keyByte);
__m128i cmp = _mm_cmplt_epi8(
_mm_set1_epi8(keyByteFlipped),
_mm_loadu_si128(reinterpret_cast<__m128i*>(this->key)));
uint16_t bitfield =
_mm_movemask_epi8(cmp) & (0xFFFF >> (16 - this->count));
unsigned pos = bitfield ? ctz(bitfield) : this->count;
memmove(this->key + pos + 1, this->key + pos, this->count - pos);
memmove(this->child + pos + 1, this->child + pos,
(this->count - pos) * sizeof(uintptr_t));
this->key[pos] = keyByteFlipped;
this->child[pos] = child;
this->count++;
} else {
// Grow to Node48
Node48* newNode = new Node48();
*nodeRef = newNode;

// update fast path
tree->fp = newNode;
tree->fp_path[tree->fp_path_length - 1] = newNode;
tree->fp_path_ref[tree->fp_path_length - 1] = nodeRef;

memcpy(newNode->child, this->child, this->count * sizeof(uintptr_t));
for (unsigned i = 0; i < this->count; i++)
newNode->childIndex[flipSign(this->key[i])] = i;
copyPrefix(this, newNode);
newNode->count = this->count;
delete this;
return newNode->lilInsertNode48(tree, nodeRef, keyByte, child);
}
}

void Node48::lilInsertNode48(ART* tree, ArtNode** nodeRef, uint8_t keyByte,
ArtNode* child) {
// Insert leaf into inner node
if (this->count < 48) {
// Insert element
unsigned pos = this->count;
if (this->child[pos])
for (pos = 0; this->child[pos] != NULL; pos++);
this->child[pos] = child;
this->childIndex[keyByte] = pos;
this->count++;
} else {
// Grow to Node256
Node256* newNode = new Node256();
for (unsigned i = 0; i < 256; i++)
if (this->childIndex[i] != 48)
newNode->child[i] = this->child[this->childIndex[i]];
newNode->count = this->count;
copyPrefix(this, newNode);
*nodeRef = newNode;

// update fast path
tree->fp = newNode;
tree->fp_path[tree->fp_path_length - 1] = newNode;
tree->fp_path_ref[tree->fp_path_length - 1] = nodeRef;

delete this;
return newNode->lilInsertNode256(tree, nodeRef, keyByte, child);
}
}

// suppress warnings about unused parameters to ensure consistency
void Node256::lilInsertNode256(ART* tree [[maybe_unused]],
ArtNode** nodeRef [[maybe_unused]],
uint8_t keyByte, ArtNode* child) {
// Insert leaf into inner node
this->count++;
this->child[keyByte] = child;
}

} // namespace ART
Loading