Skip to content

Commit 3463987

Browse files
DeusDataSelene29
andcommitted
Replace O(n*e) edge mapping with binary search in layout endpoint
Build sorted node-ID array, binary search each edge's endpoints. O(n log n + e log n) instead of O(n * e) — eliminates multi-second hangs on large projects. Rejected edges freed immediately to reduce peak memory. Fixes #169 Co-Authored-By: Selene29 <Selene29@users.noreply.github.com>
1 parent 0dc3d67 commit 3463987

File tree

1 file changed

+91
-40
lines changed

1 file changed

+91
-40
lines changed

src/ui/layout3d.c

Lines changed: 91 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -350,6 +350,36 @@ static void free_edge_array(cbm_edge_t *edges, int count) {
350350
free(edges);
351351
}
352352

353+
/* ── Node ID → index map (for O(log n) edge filtering) ───────── */
354+
355+
typedef struct {
356+
int64_t id;
357+
int idx;
358+
} node_id_entry_t;
359+
360+
static int cmp_node_id_entry(const void *a, const void *b) {
361+
int64_t da = ((const node_id_entry_t *)a)->id;
362+
int64_t db = ((const node_id_entry_t *)b)->id;
363+
return (da > db) - (da < db);
364+
}
365+
366+
static int find_node_index(const node_id_entry_t *map, int count, int64_t id) {
367+
int lo = 0;
368+
int hi = count - SKIP_ONE;
369+
while (lo <= hi) {
370+
int mid = lo + (hi - lo) / PAIR_LEN;
371+
if (map[mid].id == id) {
372+
return map[mid].idx;
373+
}
374+
if (map[mid].id < id) {
375+
lo = mid + SKIP_ONE;
376+
} else {
377+
hi = mid - SKIP_ONE;
378+
}
379+
}
380+
return CBM_NOT_FOUND;
381+
}
382+
353383
/* ── Public API ───────────────────────────────────────────────── */
354384

355385
cbm_layout_result_t *cbm_layout_compute(cbm_store_t *store, const char *project,
@@ -385,61 +415,82 @@ cbm_layout_result_t *cbm_layout_compute(cbm_store_t *store, const char *project,
385415
return r;
386416
}
387417

388-
/* 2. Query edges */
389-
int total_edges = 0, edge_cap = CBM_SZ_256;
418+
/* 2. Build sorted node-ID → index map for O(log n) edge filtering */
419+
node_id_entry_t *id_map = malloc((size_t)n * sizeof(node_id_entry_t));
420+
if (!id_map) {
421+
cbm_store_search_free(&search_out);
422+
cbm_layout_result_t *r = calloc(CBM_ALLOC_ONE, sizeof(*r));
423+
if (r) {
424+
r->total_nodes = total_count;
425+
}
426+
return r;
427+
}
428+
for (int i = 0; i < n; i++) {
429+
id_map[i].id = search_out.results[i].node.id;
430+
id_map[i].idx = i;
431+
}
432+
qsort(id_map, (size_t)n, sizeof(node_id_entry_t), cmp_node_id_entry);
433+
434+
/* 3. Query edges — filter during fetch via binary search (O(e log n)) */
435+
int *deg = calloc((size_t)n, sizeof(int));
436+
int mapped = 0;
437+
int edge_cap = CBM_SZ_256;
390438
cbm_edge_t *all_edges = malloc((size_t)edge_cap * sizeof(cbm_edge_t));
439+
int *es = malloc((size_t)edge_cap * sizeof(int));
440+
int *ed = malloc((size_t)edge_cap * sizeof(int));
391441
cbm_schema_info_t schema;
392442
memset(&schema, 0, sizeof(schema));
393-
if (cbm_store_get_schema(store, project, &schema) == CBM_STORE_OK) {
443+
if (deg && all_edges && es && ed &&
444+
cbm_store_get_schema(store, project, &schema) == CBM_STORE_OK) {
394445
for (int t = 0; t < schema.edge_type_count; t++) {
395446
cbm_edge_t *te = NULL;
396447
int tc = 0;
397448
if (cbm_store_find_edges_by_type(store, project, schema.edge_types[t].type, &te, &tc) ==
398449
CBM_STORE_OK) {
399450
for (int e = 0; e < tc; e++) {
400-
if (total_edges >= edge_cap) {
401-
edge_cap *= 2;
402-
cbm_edge_t *tmp = realloc(all_edges, (size_t)edge_cap * sizeof(cbm_edge_t));
403-
if (!tmp) {
404-
free_edge_array(te + e, tc - e);
405-
break;
451+
int si = find_node_index(id_map, n, te[e].source_id);
452+
int di = find_node_index(id_map, n, te[e].target_id);
453+
if (si >= 0 && di >= 0) {
454+
if (mapped >= edge_cap) {
455+
int nc = edge_cap * PAIR_LEN;
456+
cbm_edge_t *te2 = realloc(all_edges, (size_t)nc * sizeof(cbm_edge_t));
457+
int *ts = realloc(es, (size_t)nc * sizeof(int));
458+
int *td = realloc(ed, (size_t)nc * sizeof(int));
459+
if (!te2 || !ts || !td) {
460+
if (te2)
461+
all_edges = te2;
462+
if (ts)
463+
es = ts;
464+
if (td)
465+
ed = td;
466+
free_edge_array(te + e, tc - e);
467+
goto edges_done;
468+
}
469+
all_edges = te2;
470+
es = ts;
471+
ed = td;
472+
edge_cap = nc;
406473
}
407-
all_edges = tmp;
474+
all_edges[mapped] = te[e];
475+
memset(&te[e], 0, sizeof(cbm_edge_t));
476+
es[mapped] = si;
477+
ed[mapped] = di;
478+
deg[si]++;
479+
deg[di]++;
480+
mapped++;
481+
} else {
482+
free((void *)te[e].project);
483+
free((void *)te[e].type);
484+
free((void *)te[e].properties_json);
408485
}
409-
all_edges[total_edges++] = te[e];
410-
memset(&te[e], 0, sizeof(cbm_edge_t));
411486
}
412487
free(te);
413488
}
414489
}
490+
edges_done:
415491
cbm_store_schema_free(&schema);
416492
}
417-
418-
/* 3. Map edges + degree */
419-
int *deg = calloc((size_t)n, sizeof(int));
420-
int *es = calloc((size_t)(total_edges > 0 ? total_edges : 1), sizeof(int));
421-
int *ed = calloc((size_t)(total_edges > 0 ? total_edges : 1), sizeof(int));
422-
int mapped = 0;
423-
if (es && ed && deg) {
424-
for (int e = 0; e < total_edges; e++) {
425-
int si = -1, di = -1;
426-
for (int i = 0; i < n; i++) {
427-
if (search_out.results[i].node.id == all_edges[e].source_id)
428-
si = i;
429-
if (search_out.results[i].node.id == all_edges[e].target_id)
430-
di = i;
431-
if (si >= 0 && di >= 0)
432-
break;
433-
}
434-
if (si >= 0 && di >= 0) {
435-
es[mapped] = si;
436-
ed[mapped] = di;
437-
deg[si]++;
438-
deg[di]++;
439-
mapped++;
440-
}
441-
}
442-
}
493+
free(id_map);
443494

444495
/* 4. Call depth for z-axis */
445496
int *cdepth = calloc((size_t)n, sizeof(int));
@@ -462,7 +513,7 @@ cbm_layout_result_t *cbm_layout_compute(cbm_store_t *store, const char *project,
462513
free(ed);
463514
free(cdepth);
464515
cbm_layout_free(result);
465-
free_edge_array(all_edges, total_edges);
516+
free_edge_array(all_edges, mapped);
466517
cbm_store_search_free(&search_out);
467518
return NULL;
468519
}
@@ -545,7 +596,7 @@ cbm_layout_result_t *cbm_layout_compute(cbm_store_t *store, const char *project,
545596
free(es);
546597
free(ed);
547598
free(cdepth);
548-
free_edge_array(all_edges, total_edges);
599+
free_edge_array(all_edges, mapped);
549600
cbm_store_search_free(&search_out);
550601
return result;
551602
}

0 commit comments

Comments
 (0)