Skip to content

ltm: C-LEARN LTM compile-time dominated by per-fragment fixed overhead (DimensionsContext clone, O(D^2) intern_name, repeated parse) #655

Description

@bpowers

Context

After the 2026-05-31 LTM compile-time optimization work (commit bfff92e8 "engine: fix O(N^2) blowups in LTM compilation", through the bfff92e8..51392894 cluster on main), LTM compilation of the C-LEARN v77 model dropped from ~47.6s to ~6s. Profiling the residual ~6s shows the remaining cost is dominated by per-fragment fixed overhead that is paid once per LTM synthetic variable + helper. C-LEARN mints ~37k of these, so even small per-fragment constants dominate.

The two hot fragment-compile entry points are in src/simlin-engine/src/db/ltm/compile.rs:

  • compile_ltm_equation_fragment (around line 241; Module built at lines 934-935)
  • compile_ltm_implicit_var_fragment (around line 1187; Module built at lines 1732-1733)

Findings

1. Per-fragment deep-clone of the DimensionsContext and converted dims

Each per-fragment compiler::Module construction does:

dimensions_ctx: (*dim_context).clone(),
dimensions: converted_dims.to_vec(),

(compile.rs:934-935 and compile.rs:1732-1733)

DimensionsContext is a HashMap of dimensions with per-dimension element maps; cloning it (plus the converted-dims Vec) per fragment deep-copies the entire project dimension set ~37k times for C-LEARN.

Fix direction: make DimensionsContext / Dimension internals Arc-backed so a clone is a refcount bump, or let Module borrow them rather than own a clone.

2. Compiler::new -> populate_dimension_metadata interns names with an O(D^2) linear scan, then discards the result

Compiler::new (src/simlin-engine/src/compiler/codegen.rs:49) calls populate_dimension_metadata (codegen.rs:87), which iterates all project dimensions per fragment and interns each dimension/element name via intern_name (codegen.rs:109). intern_name is a linear scan over the names vec:

if let Some(idx) = self.names.iter().position(|n| n == name) {
    return idx as NameId;
}

That is O(D^2) string comparisons per fragment compile, repeated ~37k times. The dimension tables produced this way are discarded for fragment compiles -- the assembly stage builds its own.

Fix direction: hash-based interning for intern_name, or skip dimension-metadata population entirely for per-variable fragment compilation (the produced tables are unused on this path).

3. Each LTM equation is parsed 2-3 times during assembly

The same LTM equation text is parsed repeatedly across:

  • model_ltm_implicit_var_info
  • compile_ltm_synthetic_fragment
  • assemble_module's implicit-var loop (via parse_ltm_var_with_ids)

Fix direction: parse once and thread the parsed AST through, or memoize the parse keyed on the synthetic var.

4. Identifier canonicalization/interning under a global lock is ~15-20% of compile time

CanonicalStorage::intern (src/simlin-engine/src/common.rs:226) routes through the global sharded interner (Interner::global(), common.rs:72 -- an array of Mutex<Shard>). At ~37k fragments this lock traffic is ~15-20% of compile time.

This overlaps with #317 (intern Ident<Canonical> via salsa) -- #317 would reduce the general hashing/cloning cost, but the LTM fragment path's volume of intern calls is the specific amplifier here, and findings 1-3 above are LTM-fragment-specific and not covered by #317.

Why it matters

Maintainability and developer/agent experience: 6s to compile a single (large, but real) model with LTM enabled is slow enough to discourage iterative use of LTM on production-scale models. The remaining cost is fixed overhead multiplied by synthetic-var count, so it scales poorly as models grow. None of it is intrinsic work -- findings 1-3 are pure waste (deep-cloning immutable data, computing-then-discarding dimension tables, re-parsing identical text).

Components affected

Relationship to existing issues

Discovered

Identified during profiling of the 2026-05-31 LTM compile-time optimization work; deferred as the next layer of compile-time reduction after the O(N^2) blowups were removed.

Metadata

Metadata

Assignees

No one assigned

    Labels

    ltmLoops that Matter (LTM) analysis subsystem

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions