Skip to content

Commit d0155a0

Browse files
committed
perf: big optimization reducing GC pressure with specialized maps
1 parent 85a15d1 commit d0155a0

7 files changed

Lines changed: 439 additions & 52 deletions

File tree

engine/src/main/java/com/arcadedb/query/opencypher/executor/steps/PartitionedTriangleOp.java

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -26,15 +26,14 @@
2626
import com.arcadedb.graph.Vertex;
2727
import com.arcadedb.schema.DocumentType;
2828
import com.arcadedb.schema.VertexType;
29+
import com.arcadedb.utility.RidHashSet;
2930

3031
import com.arcadedb.query.QueryEngineManager;
3132

3233
import java.util.Arrays;
3334
import java.util.HashMap;
34-
import java.util.HashSet;
3535
import java.util.Iterator;
3636
import java.util.Map;
37-
import java.util.Set;
3837
import java.util.concurrent.ExecutionException;
3938
import java.util.concurrent.ExecutorService;
4039
import java.util.concurrent.Future;
@@ -255,7 +254,7 @@ public long executeOLTP(final Database db) {
255254
if (vCountry == null || !vCountry.equals(uCountry))
256255
continue;
257256

258-
final Set<RID> uNeighborSet = new HashSet<>();
257+
final RidHashSet uNeighborSet = new RidHashSet();
259258
for (final RID nRid : uNeighbors) {
260259
final RID nCountry = personToPartition.get(nRid);
261260
if (nCountry != null && nCountry.equals(uCountry))

engine/src/main/java/com/arcadedb/query/opencypher/executor/steps/PropagateChainOp.java

Lines changed: 22 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -25,11 +25,11 @@
2525
import com.arcadedb.graph.NeighborView;
2626
import com.arcadedb.graph.Vertex;
2727

28+
import com.arcadedb.utility.RidLongHashMap;
29+
2830
import java.util.Arrays;
29-
import java.util.HashMap;
3031
import java.util.HashSet;
3132
import java.util.Iterator;
32-
import java.util.Map;
3333
import java.util.Set;
3434

3535
/**
@@ -408,7 +408,7 @@ public long executeOLTP(final Database db) {
408408
// with its count capturing path multiplicity. This reduces O(paths) traversals
409409
// to O(unique edges), which is dramatically faster for high-fanout chains.
410410
// E.g., Q5 with 13.8M paths but only ~3.5M unique edges: ~4x fewer OLTP scans.
411-
HashMap<RID, Long> current = new HashMap<>();
411+
RidLongHashMap current = new RidLongHashMap();
412412
for (final Iterator<? extends Identifiable> it = db.iterateType(anchorLabel, true); it.hasNext(); )
413413
current.put(it.next().getIdentity(), 1L);
414414

@@ -420,24 +420,24 @@ public long executeOLTP(final Database db) {
420420
else
421421
targetBuckets = null;
422422

423-
final HashMap<RID, Long> next = new HashMap<>();
424-
for (final Map.Entry<RID, Long> entry : current.entrySet()) {
425-
final long pathCount = entry.getValue();
426-
expandNeighbors(db, provider, entry.getKey(), directions[hop], edgeTypes[hop], targetBuckets,
427-
(neighborRid) -> next.merge(neighborRid, pathCount, Long::sum));
428-
}
423+
final RidLongHashMap next = new RidLongHashMap();
424+
final int h = hop;
425+
current.forEach((bucketId, offset, pathCount) -> {
426+
final RID rid = new RID(db, bucketId, offset);
427+
expandNeighbors(db, provider, rid, directions[h], edgeTypes[h], targetBuckets,
428+
(neighborRid) -> next.add(neighborRid, pathCount));
429+
});
429430
current = next;
430431
}
431432

432-
long total = 0;
433-
for (final long c : current.values())
434-
total += c;
433+
final long[] total = {0};
434+
current.forEach((bucketId, offset, value) -> total[0] += value);
435435

436436
// Subtract self-loop paths for inequality
437437
if (inequalityIdxA >= 0 && inequalityIdxB >= 0)
438-
total -= countSelfLoopPathsOLTP(db, provider);
438+
total[0] -= countSelfLoopPathsOLTP(db, provider);
439439

440-
return total;
440+
return total[0];
441441
}
442442

443443
/**
@@ -487,7 +487,7 @@ private long countSelfLoopPathsOLTP(final Database db, final GraphTraversalProvi
487487

488488
// Sparse BFS from anchor through sub-chain [idxA, idxB)
489489
// using per-source map to deduplicate within this source's expansion
490-
HashMap<RID, Long> cur = new HashMap<>();
490+
RidLongHashMap cur = new RidLongHashMap();
491491
cur.put(anchorRid, 1L);
492492

493493
for (int h = 0; h < subLength; h++) {
@@ -499,16 +499,16 @@ private long countSelfLoopPathsOLTP(final Database db, final GraphTraversalProvi
499499
else
500500
targetBuckets = null;
501501

502-
final HashMap<RID, Long> next = new HashMap<>();
503-
for (final Map.Entry<RID, Long> entry : cur.entrySet()) {
504-
final long pathCount = entry.getValue();
505-
expandNeighbors(db, provider, entry.getKey(), directions[hopIdx], edgeTypes[hopIdx], targetBuckets,
506-
(neighborRid) -> next.merge(neighborRid, pathCount, Long::sum));
507-
}
502+
final RidLongHashMap next = new RidLongHashMap();
503+
cur.forEach((bucketId, offset, pathCount) -> {
504+
final RID rid = new RID(db, bucketId, offset);
505+
expandNeighbors(db, provider, rid, directions[hopIdx], edgeTypes[hopIdx], targetBuckets,
506+
(neighborRid) -> next.add(neighborRid, pathCount));
507+
});
508508
cur = next;
509509
}
510510

511-
long loopCount = cur.getOrDefault(anchorRid, 0L);
511+
long loopCount = cur.get(anchorRid, 0);
512512

513513
// Multiply by tail after idxB
514514
if (loopCount > 0 && idxB < edgeTypes.length) {

engine/src/main/java/com/arcadedb/query/opencypher/traversal/BreadthFirstTraverser.java

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -23,16 +23,15 @@
2323
import com.arcadedb.graph.Vertex;
2424
import com.arcadedb.query.opencypher.ast.Direction;
2525
import com.arcadedb.query.opencypher.ast.PathMode;
26+
import com.arcadedb.utility.RidHashSet;
2627

2728
import java.util.ArrayList;
28-
import java.util.HashSet;
2929
import java.util.Iterator;
3030
import java.util.LinkedList;
3131
import java.util.List;
3232
import java.util.Map;
3333
import java.util.NoSuchElementException;
3434
import java.util.Queue;
35-
import java.util.Set;
3635

3736
/**
3837
* Breadth-first graph traverser.
@@ -73,12 +72,12 @@ public Iterator<TraversalPath> traversePaths(final Vertex startVertex) {
7372
*/
7473
private class BFSVertexIterator implements Iterator<Vertex> {
7574
private final Queue<VertexWithDepth> queue = new LinkedList<>();
76-
private final Set<RID> visited;
75+
private final RidHashSet visited;
7776
private final List<Vertex> results = new ArrayList<>();
7877
private int currentIndex = 0;
7978

8079
BFSVertexIterator(final Vertex startVertex) {
81-
this.visited = detectCycles ? createVisitedSet() : new HashSet<>();
80+
this.visited = createVisitedSet();
8281
queue.add(new VertexWithDepth(startVertex, 0));
8382

8483
// Perform full BFS traversal

engine/src/main/java/com/arcadedb/query/opencypher/traversal/DepthFirstTraverser.java

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -23,14 +23,13 @@
2323
import com.arcadedb.graph.Vertex;
2424
import com.arcadedb.query.opencypher.ast.Direction;
2525
import com.arcadedb.query.opencypher.ast.PathMode;
26+
import com.arcadedb.utility.RidHashSet;
2627

2728
import java.util.ArrayList;
28-
import java.util.HashSet;
2929
import java.util.Iterator;
3030
import java.util.List;
3131
import java.util.Map;
3232
import java.util.NoSuchElementException;
33-
import java.util.Set;
3433

3534
/**
3635
* Depth-first graph traverser.
@@ -74,11 +73,11 @@ private class DFSVertexIterator implements Iterator<Vertex> {
7473
private int currentIndex = 0;
7574

7675
DFSVertexIterator(final Vertex startVertex) {
77-
final Set<RID> visited = detectCycles ? createVisitedSet() : new HashSet<>();
76+
final RidHashSet visited = createVisitedSet();
7877
performDFS(startVertex, 0, visited);
7978
}
8079

81-
private void performDFS(final Vertex vertex, final int depth, final Set<RID> visited) {
80+
private void performDFS(final Vertex vertex, final int depth, final RidHashSet visited) {
8281
// Skip if already visited
8382
if (detectCycles && isVisited(vertex, visited)) {
8483
return;

engine/src/main/java/com/arcadedb/query/opencypher/traversal/GraphTraverser.java

Lines changed: 11 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -18,17 +18,15 @@
1818
*/
1919
package com.arcadedb.query.opencypher.traversal;
2020

21-
import com.arcadedb.database.RID;
2221
import com.arcadedb.graph.Edge;
2322
import com.arcadedb.graph.Vertex;
2423
import com.arcadedb.query.opencypher.ast.Direction;
2524
import com.arcadedb.query.opencypher.ast.PathMode;
25+
import com.arcadedb.utility.RidHashSet;
2626

2727
import java.util.Collections;
28-
import java.util.HashSet;
2928
import java.util.Iterator;
3029
import java.util.Map;
31-
import java.util.Set;
3230

3331
/**
3432
* Base class for graph traversal implementations.
@@ -193,40 +191,34 @@ protected boolean matchesPropertyFilter(final Edge edge) {
193191

194192
/**
195193
* Creates a visited set for cycle detection.
196-
* Uses RIDs instead of Vertex objects for efficient O(1) hash lookups.
194+
* Uses RidHashSet for zero-boxing O(1) lookups with ~8.7x memory savings over HashSet&lt;RID&gt;.
197195
*
198-
* @return new hash set for tracking visited vertex RIDs
196+
* @return new RidHashSet for tracking visited vertex RIDs
199197
*/
200-
protected Set<RID> createVisitedSet() {
201-
return new HashSet<>();
198+
protected RidHashSet createVisitedSet() {
199+
return new RidHashSet();
202200
}
203201

204202
/**
205203
* Checks if a vertex has been visited.
206-
* O(1) hash lookup using RID.
207-
* Creates a database-independent RID to ensure proper equality/hashCode.
204+
* O(1) hash lookup using primitive bucketId + offset — no temporary RID allocation.
208205
*
209206
* @param vertex vertex to check
210207
* @param visited set of visited vertex RIDs
211208
* @return true if visited
212209
*/
213-
protected boolean isVisited(final Vertex vertex, final Set<RID> visited) {
214-
final RID rid = vertex.getIdentity();
215-
// Create database-independent RID for consistent equals/hashCode
216-
return visited.contains(new RID(rid.getBucketId(), rid.getPosition()));
210+
protected boolean isVisited(final Vertex vertex, final RidHashSet visited) {
211+
return visited.contains(vertex.getIdentity());
217212
}
218213

219214
/**
220215
* Marks a vertex as visited.
221-
* Stores only the RID for memory efficiency and O(1) lookups.
222-
* Creates a database-independent RID to ensure proper equality/hashCode.
216+
* Stores only primitive bucketId + offset for memory efficiency and O(1) lookups.
223217
*
224218
* @param vertex vertex to mark
225219
* @param visited set of visited vertex RIDs
226220
*/
227-
protected void markVisited(final Vertex vertex, final Set<RID> visited) {
228-
final RID rid = vertex.getIdentity();
229-
// Create database-independent RID for consistent equals/hashCode
230-
visited.add(new RID(rid.getBucketId(), rid.getPosition()));
221+
protected void markVisited(final Vertex vertex, final RidHashSet visited) {
222+
visited.add(vertex.getIdentity());
231223
}
232224
}

0 commit comments

Comments
 (0)