mirror of
https://github.com/DeusData/codebase-memory-mcp.git
synced 2026-11-03 08:03:38 +08:00
perf(lsp): index C++ short-name type lookups
Avoid repeated full type-registry scans in the namespaced-type fallback. Build the optional index only for C/C++ cross registries while preserving candidate order, post-finalize tails, and allocation fallback. Refs #1677 Signed-off-by: astandrik <astandrik@yandex-team.ru>
This commit is contained in:
@@ -2818,7 +2818,10 @@ static const CBMRegisteredFunc *c_lookup_member_depth(CLSPContext *ctx, const ch
|
||||
const char *shortn = dot ? dot + 1 : type_qn;
|
||||
size_t slen = strlen(shortn);
|
||||
const char *best_qn = NULL;
|
||||
for (int i = 0; i < ctx->registry->type_count; i++) {
|
||||
CBMTypeNameIter it;
|
||||
cbm_registry_types_by_short_name(ctx->registry, shortn, &it);
|
||||
int i;
|
||||
while ((i = cbm_type_name_iter_next(&it)) >= 0) {
|
||||
const char *q = ctx->registry->types[i].qualified_name;
|
||||
if (!q) {
|
||||
continue;
|
||||
@@ -5972,6 +5975,7 @@ CBMTypeRegistry *cbm_c_build_cross_registry(CBMArena *arena, CBMLSPDef *defs, in
|
||||
c_register_lsp_defs(arena, reg, "", d, 1);
|
||||
}
|
||||
cbm_registry_finalize(reg);
|
||||
cbm_registry_build_type_short_index(reg);
|
||||
reg->read_only = true; /* seal: shared Tier-2 registry is read-only during resolve */
|
||||
return reg;
|
||||
}
|
||||
@@ -6055,6 +6059,7 @@ void cbm_run_c_lsp_cross(CBMArena *arena, const char *source, int source_len, co
|
||||
// Finalize registry — O(1) lookups. See go_lsp.c "3c. Finalize"
|
||||
// comment for the rationale (linear-scan fallback otherwise).
|
||||
cbm_registry_finalize(®);
|
||||
cbm_registry_build_type_short_index(®);
|
||||
|
||||
// Initialize context and run
|
||||
CLSPContext ctx;
|
||||
|
||||
@@ -167,6 +167,51 @@ static void build_type_embed_index(CBMTypeRegistry *reg, CBMArena *idx_arena) {
|
||||
reg->type_embed_entry_count = idx;
|
||||
}
|
||||
|
||||
/* Optional index: final qualified-name segment -> chain of TYPE indices.
|
||||
* Descending iteration plus prepend preserves ascending registry scan order.
|
||||
* Consumers opt in after the QN index captures the finalized tail boundary. */
|
||||
void cbm_registry_build_type_short_index(CBMTypeRegistry *reg) {
|
||||
if (!reg || !reg->arena || !reg->type_qn_buckets || reg->type_qn_bucket_count <= 0)
|
||||
return;
|
||||
CBMArena *idx_arena = reg->arena;
|
||||
int count = 0;
|
||||
for (int i = 0; i < reg->type_count; i++) {
|
||||
if (reg->types[i].qualified_name)
|
||||
count++;
|
||||
}
|
||||
if (count == 0)
|
||||
return;
|
||||
int bucket_count = next_pow2(count * 2);
|
||||
if (bucket_count < 16)
|
||||
bucket_count = 16;
|
||||
int *buckets = (int *)cbm_arena_alloc(idx_arena, (size_t)bucket_count * sizeof(int));
|
||||
CBMRegistryHashEntry *entries = (CBMRegistryHashEntry *)cbm_arena_alloc(
|
||||
idx_arena, (size_t)count * sizeof(CBMRegistryHashEntry));
|
||||
if (!buckets || !entries)
|
||||
return;
|
||||
for (int i = 0; i < bucket_count; i++)
|
||||
buckets[i] = -1;
|
||||
int idx = 0;
|
||||
for (int i = reg->type_count - 1; i >= 0; i--) {
|
||||
const char *qn = reg->types[i].qualified_name;
|
||||
if (!qn)
|
||||
continue;
|
||||
const char *dot = strrchr(qn, '.');
|
||||
const char *short_name = dot ? dot + 1 : qn;
|
||||
uint64_t h = fnv1a(short_name);
|
||||
int slot = (int)(h & (uint64_t)(bucket_count - 1));
|
||||
entries[idx].hash = h;
|
||||
entries[idx].payload_index = i;
|
||||
entries[idx].next_index = buckets[slot];
|
||||
entries[idx].slot = slot;
|
||||
buckets[slot] = idx;
|
||||
idx++;
|
||||
}
|
||||
reg->type_short_buckets = buckets;
|
||||
reg->type_short_entries = entries;
|
||||
reg->type_short_bucket_count = bucket_count;
|
||||
}
|
||||
|
||||
/* Index: short_name -> chain of FREE-function (receiver_type==NULL) indices.
|
||||
* Descending-iterate + prepend for ascending chain order (as above). */
|
||||
static void build_ffunc_short_index(CBMTypeRegistry *reg, CBMArena *idx_arena) {
|
||||
@@ -208,6 +253,43 @@ static void build_ffunc_short_index(CBMTypeRegistry *reg, CBMArena *idx_arena) {
|
||||
reg->ffunc_short_entry_count = idx;
|
||||
}
|
||||
|
||||
void cbm_registry_types_by_short_name(const CBMTypeRegistry *reg, const char *short_name,
|
||||
CBMTypeNameIter *out) {
|
||||
out->reg = reg;
|
||||
out->hash = fnv1a(short_name);
|
||||
if (reg->type_qn_buckets && reg->type_qn_bucket_count > 0) {
|
||||
if (reg->type_short_buckets && reg->type_short_bucket_count > 0) {
|
||||
int slot = (int)(out->hash & (uint64_t)(reg->type_short_bucket_count - 1));
|
||||
out->chain_idx = reg->type_short_buckets[slot];
|
||||
out->tail_i = reg->type_qn_entry_count;
|
||||
} else {
|
||||
out->chain_idx = -1;
|
||||
out->tail_i = 0; /* auxiliary-index allocation failed: preserve full-scan semantics */
|
||||
}
|
||||
out->tail_end = reg->type_count;
|
||||
} else {
|
||||
out->chain_idx = -1;
|
||||
out->tail_i = 0;
|
||||
out->tail_end = reg->type_count;
|
||||
}
|
||||
}
|
||||
|
||||
int cbm_type_name_iter_next(CBMTypeNameIter *it) {
|
||||
const CBMTypeRegistry *reg = it->reg;
|
||||
while (it->chain_idx >= 0) {
|
||||
const CBMRegistryHashEntry *e = ®->type_short_entries[it->chain_idx];
|
||||
int p = e->payload_index;
|
||||
uint64_t h = e->hash;
|
||||
it->chain_idx = e->next_index;
|
||||
if (h != it->hash)
|
||||
continue;
|
||||
return p;
|
||||
}
|
||||
if (it->tail_i < it->tail_end)
|
||||
return it->tail_i++;
|
||||
return -1;
|
||||
}
|
||||
|
||||
void cbm_registry_types_by_embedded_bare(const CBMTypeRegistry *reg, const char *bare,
|
||||
CBMTypeEmbedIter *out) {
|
||||
out->reg = reg;
|
||||
|
||||
@@ -111,9 +111,14 @@ typedef struct CBMTypeRegistry {
|
||||
int method_bucket_count;
|
||||
int method_entry_count;
|
||||
|
||||
// Auxiliary short-name / embedded-type indexes (built by finalize alongside the
|
||||
// QN buckets). Turn the Rust trait- and free-function fallback scans from
|
||||
// O(type_count)/O(func_count) into O(chain). Read-only after finalize.
|
||||
// Auxiliary short-name / embedded-type indexes. The type short-name index is
|
||||
// opt-in after finalize; the others are built by finalize. Current C/C++ cross
|
||||
// registries opt in, while other languages incur no construction cost.
|
||||
// Type short-name index: fnv1a(last-'.'-segment of qualified_name) -> chain
|
||||
// of TYPE indices. payload_index = type index.
|
||||
int *type_short_buckets;
|
||||
CBMRegistryHashEntry *type_short_entries;
|
||||
int type_short_bucket_count;
|
||||
// Embedded-type index: fnv1a(bare last-'.'-segment of each embedded_type) -> chain
|
||||
// of TYPE indices declaring it. payload_index = type index (a type may appear once
|
||||
// per embedded entry; consumers dedup adjacent same-type via the iterator).
|
||||
@@ -156,6 +161,11 @@ void cbm_registry_finalize(CBMTypeRegistry *reg);
|
||||
// there add GBs across a large repo (FastAPI incremental test: +1.1 GB RSS).
|
||||
void cbm_registry_finalize_into(CBMTypeRegistry *reg, CBMArena *idx_arena);
|
||||
|
||||
// Build the optional final-QN-segment type index. Call immediately after
|
||||
// finalization. Consumers that do not opt in avoid its type_count-sized allocation
|
||||
// and scan. Allocation failure preserves the iterator's linear fallback.
|
||||
void cbm_registry_build_type_short_index(CBMTypeRegistry *reg);
|
||||
|
||||
// Register a function/method.
|
||||
void cbm_registry_add_func(CBMTypeRegistry *reg, CBMRegisteredFunc func);
|
||||
|
||||
@@ -215,8 +225,24 @@ const CBMRegisteredFunc *cbm_registry_lookup_symbol_by_types(const CBMTypeRegist
|
||||
const CBMType **arg_types,
|
||||
int arg_count);
|
||||
|
||||
// --- Auxiliary index iterators (Rust trait / free-function fallback fast paths) ---
|
||||
// --- Auxiliary index iterators (language fallback fast paths) ---
|
||||
//
|
||||
// Iterate registry TYPE indices whose qualified-name final segment may equal
|
||||
// `short_name`. When the optional index is present this walks its hash chain plus
|
||||
// any post-finalize tail; otherwise it degrades to the original full scan. Results
|
||||
// preserve ascending registry order. The index is a hash prefilter; callers must
|
||||
// re-check their exact predicate, including tail entries.
|
||||
typedef struct {
|
||||
const CBMTypeRegistry *reg;
|
||||
uint64_t hash;
|
||||
int chain_idx;
|
||||
int tail_i;
|
||||
int tail_end;
|
||||
} CBMTypeNameIter;
|
||||
void cbm_registry_types_by_short_name(const CBMTypeRegistry *reg, const char *short_name,
|
||||
CBMTypeNameIter *out);
|
||||
int cbm_type_name_iter_next(CBMTypeNameIter *it);
|
||||
|
||||
// Iterate registry TYPE indices whose embedded_types contain an entry whose BARE
|
||||
// name (last '.'-segment) equals `bare`. On a finalized registry this walks the
|
||||
// embedded-type index plus any post-finalize tail; on an unfinalized registry it
|
||||
|
||||
+105
-6
@@ -15250,12 +15250,9 @@ TEST(clsp_tier2_shared_registry_readonly_c) {
|
||||
PASS();
|
||||
}
|
||||
|
||||
/* Direct guard for the finalize-time short-name / embedded-type indexes and their
|
||||
* iterators (type_registry.c), which the Rust trait/free-func fast paths rely on.
|
||||
* Verifies: embed index yields exactly the types whose embedded_types carry a
|
||||
* matching BARE name, in ascending registry order, deduped when a type lists the
|
||||
* same bare twice; free-func index yields only free funcs (receiver_type==NULL) with
|
||||
* the given short_name, not methods. */
|
||||
/* Direct guard for the type-name / embedded-type / free-function registry
|
||||
* indexes and their iterators (type_registry.c). Verifies that every iterator
|
||||
* preserves ascending registry order, which is part of resolver tie-breaking. */
|
||||
TEST(registry_short_name_indexes) {
|
||||
CBMArena arena;
|
||||
cbm_arena_init(&arena);
|
||||
@@ -15288,6 +15285,25 @@ TEST(registry_short_name_indexes) {
|
||||
t.short_name = "C";
|
||||
t.embedded_types = c_emb;
|
||||
cbm_registry_add_type(®, t);
|
||||
memset(&t, 0, sizeof(t));
|
||||
t.qualified_name = "other.Trait";
|
||||
t.short_name = "Trait";
|
||||
cbm_registry_add_type(®, t);
|
||||
memset(&t, 0, sizeof(t));
|
||||
t.qualified_name = "Trait";
|
||||
t.short_name = "Trait";
|
||||
cbm_registry_add_type(®, t);
|
||||
|
||||
/* Populate enough distinct names to force many hash-bucket collisions, with
|
||||
* repeated short names spread across the registry. The differential check
|
||||
* below compares the index against the exact former linear-scan order. */
|
||||
for (int i = 0; i < 512; i++) {
|
||||
memset(&t, 0, sizeof(t));
|
||||
t.qualified_name = cbm_arena_sprintf(&arena, "bulk.mod%d.Name%d", i, i % 23);
|
||||
ASSERT_NOT_NULL(t.qualified_name);
|
||||
t.short_name = strrchr(t.qualified_name, '.') + 1;
|
||||
cbm_registry_add_type(®, t);
|
||||
}
|
||||
|
||||
/* free func "helper" (x2 — different QNs), method "M.helper", free func "other". */
|
||||
CBMRegisteredFunc f;
|
||||
@@ -15310,6 +15326,54 @@ TEST(registry_short_name_indexes) {
|
||||
cbm_registry_add_func(®, f);
|
||||
|
||||
cbm_registry_finalize(®);
|
||||
cbm_registry_build_type_short_index(®);
|
||||
|
||||
/* Qualified-name bare segment "Trait": types 0, 4, then the bare-QN type
|
||||
* 5. C-LSP applies its stricter dotted-QN predicate on top of this index. */
|
||||
CBMTypeNameIter nit;
|
||||
cbm_registry_types_by_short_name(®, "Trait", &nit);
|
||||
ASSERT_EQ(cbm_type_name_iter_next(&nit), 0);
|
||||
ASSERT_EQ(cbm_type_name_iter_next(&nit), 4);
|
||||
ASSERT_EQ(cbm_type_name_iter_next(&nit), 5);
|
||||
ASSERT_EQ(cbm_type_name_iter_next(&nit), -1);
|
||||
cbm_registry_types_by_short_name(®, "Missing", &nit);
|
||||
ASSERT_EQ(cbm_type_name_iter_next(&nit), -1);
|
||||
|
||||
for (int name_i = 0; name_i < 23; name_i++) {
|
||||
char short_name[32];
|
||||
snprintf(short_name, sizeof(short_name), "Name%d", name_i);
|
||||
cbm_registry_types_by_short_name(®, short_name, &nit);
|
||||
int linear_i = 0;
|
||||
for (;;) {
|
||||
int expected = -1;
|
||||
while (linear_i < reg.type_count) {
|
||||
int candidate = linear_i++;
|
||||
const char *qn = reg.types[candidate].qualified_name;
|
||||
const char *last_dot = qn ? strrchr(qn, '.') : NULL;
|
||||
const char *candidate_short = last_dot ? last_dot + 1 : qn;
|
||||
if (candidate_short && strcmp(candidate_short, short_name) == 0) {
|
||||
expected = candidate;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
int actual;
|
||||
for (;;) {
|
||||
actual = cbm_type_name_iter_next(&nit);
|
||||
if (actual < 0)
|
||||
break;
|
||||
const char *qn = reg.types[actual].qualified_name;
|
||||
const char *last_dot = qn ? strrchr(qn, '.') : NULL;
|
||||
const char *candidate_short = last_dot ? last_dot + 1 : qn;
|
||||
if (candidate_short && strcmp(candidate_short, short_name) == 0)
|
||||
break;
|
||||
}
|
||||
|
||||
ASSERT_EQ(actual, expected);
|
||||
if (expected < 0)
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/* Embed index for bare "Trait": types 1 (A) then 2 (B), ascending, B once. */
|
||||
CBMTypeEmbedIter it;
|
||||
@@ -15339,6 +15403,41 @@ TEST(registry_short_name_indexes) {
|
||||
ASSERT_EQ(cbm_free_func_iter_next(&fit), 3);
|
||||
ASSERT_EQ(cbm_free_func_iter_next(&fit), -1);
|
||||
|
||||
/* A type appended after finalize is covered by the iterator tail. If the
|
||||
* auxiliary allocation is unavailable, the iterator falls back to the same
|
||||
* complete linear scan rather than silently dropping candidates. Keep this
|
||||
* after the other iterator assertions because their tails intentionally
|
||||
* expose post-finalize candidates for caller-side exact filtering too. */
|
||||
int tail_type_i = reg.type_count;
|
||||
memset(&t, 0, sizeof(t));
|
||||
t.qualified_name = "tail.Trait";
|
||||
t.short_name = "Trait";
|
||||
cbm_registry_add_type(®, t);
|
||||
cbm_registry_types_by_short_name(®, "Trait", &nit);
|
||||
ASSERT_EQ(cbm_type_name_iter_next(&nit), 0);
|
||||
ASSERT_EQ(cbm_type_name_iter_next(&nit), 4);
|
||||
ASSERT_EQ(cbm_type_name_iter_next(&nit), 5);
|
||||
ASSERT_EQ(cbm_type_name_iter_next(&nit), tail_type_i);
|
||||
ASSERT_EQ(cbm_type_name_iter_next(&nit), -1);
|
||||
|
||||
int *saved_type_short_buckets = reg.type_short_buckets;
|
||||
reg.type_short_buckets = NULL;
|
||||
cbm_registry_types_by_short_name(®, "Trait", &nit);
|
||||
int fallback_expected[] = {0, 4, 5, tail_type_i};
|
||||
int fallback_count = 0;
|
||||
int candidate;
|
||||
while ((candidate = cbm_type_name_iter_next(&nit)) >= 0) {
|
||||
const char *qn = reg.types[candidate].qualified_name;
|
||||
const char *last_dot = qn ? strrchr(qn, '.') : NULL;
|
||||
const char *candidate_short = last_dot ? last_dot + 1 : qn;
|
||||
if (!candidate_short || strcmp(candidate_short, "Trait") != 0)
|
||||
continue;
|
||||
ASSERT_TRUE(fallback_count < 4);
|
||||
ASSERT_EQ(candidate, fallback_expected[fallback_count++]);
|
||||
}
|
||||
ASSERT_EQ(fallback_count, 4);
|
||||
reg.type_short_buckets = saved_type_short_buckets;
|
||||
|
||||
cbm_arena_destroy(&arena);
|
||||
PASS();
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user