Date: 2026-02-23
Component: OntologyGenerator._extract_rule_based()
Scope: All extraction-related hot paths identified via cProfile
Performance profiling of the extraction pipeline reveals relationship inference is the primary bottleneck, accounting for 60-80% of total execution time. The algorithm exhibits superlinear O(n²+) scaling, where a 32x increase in document size results in a 61x execution time increase.
| Document Size | Avg Time | Entities | Relationships | Scaling |
|---|---|---|---|---|
| 44 chars (Small) | 0.68ms | 4 | 2 | Baseline |
| 482 chars (Medium) | 3.60ms | 40 | 224 | 5.3x |
| 1404 chars (Large) | 40.94ms | 156 | 1556 | 60.6x |
Conclusion: Time scales faster than linear growth in entity count, confirming O(n²+) complexity.
Total time per run: ~41ms
Components:
- Relationship Inference: ~33-35ms (80-85%)
- Entity Extraction: ~5-6ms (12-15%)
- Pattern Building: <1ms (1-2%)
Operation: Compare all entity pairs (n×(n-1)/2) for relationship patterns
For Large Document (156 entities):
- Entity pairs to compare: 12,090
- Actual relationships found: 1,556 (12.9% density)
- Regex operations: ~3-4 per pair = ~40,000 regex searches
- Per-pair time: ~3.4 microseconds
Hotspots in infer_relationships():
re.finditer()calls on full text for each pattern- Type confidence scoring decision tree
- Entity text lookup (string comparison)
Why O(n²+)?
Total Time = O(n²) entity pairs × O(m) verb patterns × O(t) text search
= O(n² × m × t)
where:
n = number of entities
m = number of verb patterns (~20)
t = text search complexity ≈ O(|text|) for regex finditer
Real-world example:
- 4 entities → 6 pairs × 20 patterns × 1404 chars ≈ 169K operations
- 156 entities → 12,090 pairs × 20 patterns × 1404 chars ≈ 339M operations
Effort: Low
Potential Impact: 20-30% reduction
Status: Implemented 2026-02-23
Implementation: Skip relationship inference for entity type pairs that are unlikely to relate (e.g., Date-to-Date).
Benchmark Note: Initial scaling sweep shows modest gains at higher entity counts (~13% at 100 entities) with minimal impact at lower sizes. Use the prefilter toggle in bench_infer_relationships_scaling.py for regression tracking.
Code pattern:
# Skip certain type combinations
IMPOSSIBLE_PAIRS = {
('Date', 'Date'),
('MonetaryAmount', 'Location'),
('Concept', 'Concept'),
}
if (e1.type, e2.type) in IMPOSSIBLE_PAIRS:
continue # Skip this pairEffort: Moderate
Potential Impact: 35-45% reduction
Status: Implemented 2026-02-23 (config: sentence_window)
Implementation: Only search within +/- N sentences of entity mentions.
Rationale: Relationships typically expressed within 1-3 sentences; searching entire document is wasteful.
Effort: High
Potential Impact: 4-8x speedup (on multi-core)
Status: Implemented 2026-02-23 (config: enable_parallel_inference, max_workers)
Implementation: Process entity pairs in parallel using concurrent.futures.ThreadPoolExecutor.
Design Notes:
- Entity pairs partitioned evenly across worker threads
- Thread-safe relationship ID generation using
threading.Lock - Pre-computation of sentence indices (if sentence_window enabled) for thread safety
- Fallback to serial processing for small entity counts (<10) for efficiency
- Compatible with both P1 prefiltering and P2 sentence-window limiting
Configuration:
config = ExtractionConfig(
enable_parallel_inference=True,
max_workers=4 # Adjust based on CPU cores
)Test Coverage: 9 tests validating parallel correctness, thread safety, and interaction with other optimizations.
Approach:
- Partition entity pairs into chunks
- Process chunks in parallel workers
- Merge results
Effort: Very High
Potential Impact: 50-70% reduction
Implementation: Replace regex with compiled string search (Aho-Corasick automaton or similar).
Trade-offs: Requires external library, but eliminates Python regex overhead.
Current: ~41ms for 156 entities
After P1 + P2: ~15-20ms (60% reduction)
After P3 (parallelization): ~3-5ms (87% reduction on 4-core system)
- ✅ Document profiling (DONE) - baseline established
- ✅ Implement type filtering (DONE) - in
infer_relationships()prefilter path - ✅ Add sentence-based limiting (DONE) - uses
ExtractionConfig.sentence_window - Parallel relationship scoring (P3) - 3-4 hours
- Vectorized string matching (P4) - Research phase
- Create benchmark suite (DONE:
bench_infer_relationships_scaling.py) - Track per-optimization performance deltas
- Monitor for accuracy regressions
- Execution time (primary)
- Relationship precision/recall (accuracy)
- Memory usage (secondary)
- CPU utilization (for parallelization)
Scripts Created:
scripts/profile_extraction_hotpaths.py- Basic timing analysisscripts/profile_extraction_cprofile.py- Detailed cProfile analysis
Benchmarks Created:
benchmarks/bench_infer_relationships_scaling.py- Entity count scaling + prefilter togglebenchmarks/bench_relationship_type_confidence_scoring.py- Type scoring overhead
- OpenTelemetry spans in OntologyPipeline (enable_tracing=True) for production insights
- Profiling infrastructure in
common/profiling.pyfor continuous monitoring - ExtractionConfig.max_relationships for limiting output
Analysis completed by: Copilot
Status: P1 implemented; ready for P2
Next Step: Implement sentence-based limiting to reduce full-text scans