perf: implement structural sharing for expression trees #10
Reference in New Issue
Block a user
Delete Branch "perf/structural-sharing"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Description
The profiler revealed that 75% of CPU time was spent on memory allocation, with the primary bottleneck being expression copying during variable substitution. Every time a variable was substituted with an expression,
replacement.Copy()would create a full deep copy of the entire expression tree.This PR refactors the lambda calculus interpreter from a mutable, pointer-based implementation to an immutable, structurally-shared implementation. Expressions are now immutable value types that share unchanged subtrees instead of copying them.
Key changes:
Substitute()andRename()from in-place mutation to functional methods that return new expressions.Copy()method entirely - no more deep copying during substitution.Decisions
Immutability over mutation: Switched from mutable
*Expressionpointers with in-place updates to immutable expressions that return new trees. This is a fundamental architectural shift but aligns with functional programming principles and enables structural sharing.Structural sharing strategy: When
Substitute()orRename()encounters an unchanged subtree, it returns the original pointer instead of creating a new object. This is safe because expressions are now immutable.Field encapsulation: Made all expression fields unexported (
Parameter→parameter,Body→body, etc.) to prevent external mutation. Added getter methods for controlled access.Benefits
Performance improvements (measured across all samples):
Wall-clock improvements:
Memory allocation eliminated:
runtime.mallocgcSmallScanNoHeaderconsumed 10-50ms per sampleCopy()method calls removed from hot pathThe optimization in action:
Before:
After:
Codebase improvements:
Checklist
perf/structural-sharing).