Skip to content

Add a C port, and speedup Rust - #56

Draft
certik wants to merge 7 commits into
ellisonbg:mainfrom
certik:c-port
Draft

Add a C port, and speedup Rust#56
certik wants to merge 7 commits into
ellisonbg:mainfrom
certik:c-port

Conversation

@certik

@certik certik commented Jul 9, 2026

Copy link
Copy Markdown
Collaborator

This is a draft, we'll extract useful parts into separate PRs / projects.

certik and others added 7 commits July 9, 2026 07:05
Documents expression representation, ExprRef handles, interner mechanics
(two-level IndexSet structure, shallow hashing/equality), automatic
canonicalization rules, and traced walkthroughs of x+x and diff(sin(x), x).
Linked from spec/design.md and README.md.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014GcRnPSHE7EazAEDdrfUcj
C11 implementation of the Number/Symbol/Pow/Mul/Add subset with the same
design and algorithms as the Rust core: arena-backed hash-consing interner
(dense node array + open-addressing index table, FxHash, shallow
hashing/equality), canonicalizing constructors with the same rule order
and binary fast paths, structural ordering, and memoized expand with
multinomial expansion using overflow-checked __int128 coefficients.

Custom infrastructure, no hot-path malloc: uint32_t ExprRef handles,
node/child/name arenas, open-addressing i128 accumulator maps and
expand cache, reusable scratch buffers.

Tests verify hash-consing identity, canonicalization rules, exact display
strings, benchmark term counts (21/66/2^n/56/5984/6272/153), and that
expansion preserves value under a modular evaluator; suite is
ASan/UBSan-clean. bench/bench.c mirrors the Criterion expand scenarios
(fresh context per iteration, scenario body only); C runs 1.2-2x faster
than the Rust core on this machine.

Implemented per c/PLAN.md by a Sonnet 5 subagent.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014GcRnPSHE7EazAEDdrfUcj
Speeds up the expand benchmarks 10-32% with identical algorithms and
canonical forms (all 1479 Rust + 255 Python tests unchanged):

- ExprRef is u32 instead of usize: halves every child slice and handle.
- New default interner (FastInterner): dense Vec<Expr> + hashbrown
  HashTable of (cached hash, index) slots, mirroring the C interner.
  Borrowed-candidate methods (intern_{add,mul,ncmul}_{slice,vec},
  intern_mul_tail, intern_add_head) probe without building a Box, so
  interning an existing node performs no allocation; Vec-based variants
  reuse the buffer when the node is new. IndexSetInterner retained as a
  reference implementation.
- Context-owned pools for the Add/Mul accumulator maps and finalizer
  scratch buffers (cleared, capacity retained) instead of fresh
  FxHashMap/Vec per canonicalization.
- Binary add/mul dispatch on iterator size_hint: two-element calls run
  the fast paths with zero allocation; larger inputs keep in-place Vec
  collection.
- try_mul_fast/add_number_to_sorted read children in place (plan phase
  with shared borrows, then one exact-capacity build) instead of
  defensively copying; collect_mul_child takes &Context and walks
  nested children without to_vec; collect_add_child re-fetches by index.
- is_commutative memoized per node (nodes are immutable and interned
  bottom-up, so commutativity is fixed once computed).
- propagate_errors short-circuits via a context-level has_errors flag:
  hash-consing guarantees no child can be an Error if none was interned.
- #[inline] on Number add/mul.

expand benchmarks vs the C port (median, same machine): expand3 52.5ms
(C 53.4ms), product_of_binary_sums_20 822ms (C 810ms), expand1 3.55ms
(C 3.30ms), expand2 220ms (C 167ms). add1 34.9ms (was 42.6ms).

Adds profile_expand2 bin target for sampling the expand2 workload.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014GcRnPSHE7EazAEDdrfUcj
Replace Box<[ExprRef]> with a new Children type for every
slice-children Expr variant (Add, Mul, NCMul, TensorProduct, Function,
Sum, Product, Integral, Derivative, Operator, Ket, Bra, And, Or, Xor,
Piecewise, FiniteSet, Union, Intersection). Lists of up to 8 children
are stored directly in the node — no heap allocation and no pointer
chase on access; longer lists spill to a heap slice as before.

Expr stays 64 bytes (the Number variant dominates), so the inline
storage is free space-wise. The representation is canonical (len <= 8
is always inline), and Children hashes and compares as its slice view,
so the interner's borrowed-candidate probes are unaffected. Children
derefs to [ExprRef]; destructuring match sites are unchanged.

Expand benchmarks improve another 3-5% (quiet-machine medians:
binomial_20 7.89us, trinomial_10 22.7us, polynomial_5 17.4us,
expand1 3.41ms, expand2 214ms, expand3 53.0ms). The C port comparison
now stands at: expand3 and product_of_binary_sums_20 at parity or
faster, expand1 within 3%, expand2 at 1.28x.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014GcRnPSHE7EazAEDdrfUcj
…steps

Adds spec/performance.md: the C port comparison methodology, current
benchmark table (Rust at parity with C on expand1/expand3/pobs_20,
1.2-1.7x elsewhere), the optimizations that landed with their measured
effects, negative results preserved so they are not re-tried
(target-cpu=native, pre-sized interner tables, single- vs two-probe
interning, and the 48-byte-node experiment on the
experiment-48-byte-nodes branch), where the remaining gap lives, and
ranked next steps (INLINE_CAP 11, integer fast-path accumulation,
Tier-2 arena redesign as a proposed spec, PGO).

Updates spec/internals.md and spec/accepted/expressions.md to describe
the current implementation (ExprRef(u32), FastInterner with cached-hash
slots and borrowed-candidate probes, inline Children storage), and
links performance.md from design.md and the README.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014GcRnPSHE7EazAEDdrfUcj
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant