mirror of
https://github.com/supermemoryai/supermemory.git
synced 2026-10-11 03:37:56 +00:00
perf(memory-graph): fix O(n^2) BFS queue drain in cluster assignment
computeClusterAssignments drained its BFS frontier with Array.shift(), which is O(remaining length) per call. Wide-frontier components (e.g. a hub memory that many others relate to) made the drain O(n^2) on every memory-graph load/update. Swapped to an index-pointer dequeue, which preserves identical traversal order and output.
This commit is contained in:
parent
fc56bb798b
commit
b22d3e0087
2 changed files with 28 additions and 2 deletions
|
|
@ -140,6 +140,27 @@ describe("cluster assignments", () => {
|
||||||
|
|
||||||
expect(assignments.get("a1")?.key).toBe(assignments.get("b1")?.key)
|
expect(assignments.get("a1")?.key).toBe(assignments.get("b1")?.key)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
it("merges a wide fan-out of memories relating to one hub into a single cluster", () => {
|
||||||
|
// Regression test for the BFS queue drain: a hub with many direct
|
||||||
|
// relations produces a wide frontier, which previously interacted
|
||||||
|
// badly with an O(n) `Array.shift()` dequeue.
|
||||||
|
const hub = makeDocument("doc-hub", [makeMemory({ id: "hub" })])
|
||||||
|
const spokes = Array.from({ length: 200 }, (_, i) =>
|
||||||
|
makeDocument(`doc-${i}`, [
|
||||||
|
makeMemory({ id: `spoke-${i}`, memoryRelations: { hub: "derives" } }),
|
||||||
|
]),
|
||||||
|
)
|
||||||
|
|
||||||
|
const assignments = computeClusterAssignments([hub, ...spokes])
|
||||||
|
|
||||||
|
const hubKey = assignments.get("hub")?.key
|
||||||
|
expect(hubKey).toBeDefined()
|
||||||
|
for (let i = 0; i < spokes.length; i++) {
|
||||||
|
expect(assignments.get(`spoke-${i}`)?.key).toBe(hubKey)
|
||||||
|
}
|
||||||
|
expect(assignments.get("hub")?.size).toBe(spokes.length + 1)
|
||||||
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
describe("memory orbit placement", () => {
|
describe("memory orbit placement", () => {
|
||||||
|
|
|
||||||
|
|
@ -157,10 +157,15 @@ export function computeClusterAssignments(
|
||||||
|
|
||||||
const component: string[] = []
|
const component: string[] = []
|
||||||
const queue = [startId]
|
const queue = [startId]
|
||||||
|
// Index pointer instead of Array.shift(): shift() is O(remaining
|
||||||
|
// length) per call, so draining a wide BFS frontier (e.g. a heavily
|
||||||
|
// cross-referenced "hub" memory with many direct relations) was
|
||||||
|
// O(n^2) for large components.
|
||||||
|
let head = 0
|
||||||
visited.add(startId)
|
visited.add(startId)
|
||||||
|
|
||||||
while (queue.length > 0) {
|
while (head < queue.length) {
|
||||||
const id = queue.shift() as string
|
const id = queue[head++] as string
|
||||||
component.push(id)
|
component.push(id)
|
||||||
for (const nextId of adjacency.get(id) ?? []) {
|
for (const nextId of adjacency.get(id) ?? []) {
|
||||||
if (visited.has(nextId)) continue
|
if (visited.has(nextId)) continue
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue