Comprehensive performance characteristics and benchmarks for rust-rule-engine.
- Fact Insertion: ~4µs per fact (1000 facts benchmark)
- Type Lookup: O(1) HashMap-based indexing
- Update Tracking: Constant time modification detection
- Selective Re-evaluation: 2x speedup vs full re-evaluation
- Affected Rules Only: Only re-evaluate rules depending on changed fact types
- Best For: Large rule sets (>100 rules) with frequent updates
- Cache Hit Rate: 99.99% in optimal scenarios
- Speedup: 5-20x for repeated pattern evaluations
- Overhead: Minimal (~100-200ns for hash lookup)
- Memory: Hash-based cache, configurable size
- Validation Cost: 1-2µs per fact
- Overhead: One-time schema compilation
- Use Case: Type safety with negligible performance impact
- Read Access: ~120ns (RwLock read)
- Write Access: ~180ns (RwLock write)
- Increment: ~190ns (atomic numeric operation)
- Thread Safety: Arc with minimal contention
- Simple Rules: ~10µs per rule evaluation
- Complex Conditions: ~20-50µs depending on complexity
- Plugin Actions: 2-5µs per action call
- Get/Set: O(1) HashMap operations
- Nested Access: ~2-3µs for deep paths
- Serialization: ~10-20µs per Facts object
- Rule Loading: ~50µs per rule from GRL
- Salience Sorting: O(n log n) one-time cost
- Rule Selection: O(1) priority queue
| Metric | Native Engine | RETE-UL Engine | Winner |
|---|---|---|---|
| Initial Load | 5ms | 8ms | Native |
| First Execution | 1.2ms | 0.8ms | RETE |
| Repeated Execution | 1.0ms | 0.1ms (memoized) | RETE |
| Single Fact Update | 1.2ms (full) | 0.4ms (incremental) | RETE |
| Memory Usage | 2MB | 3.5MB | Native |
| Startup Time | 1ms | 5ms | Native |
Recommendation:
- < 50 rules: Native Engine (lower overhead)
- > 100 rules: RETE-UL Engine (better scalability)
Rules | Native Exec | RETE Exec | RETE Advantage
-------|-------------|-----------|---------------
10 | 0.1ms | 0.15ms | None
50 | 0.5ms | 0.4ms | 1.25x
100 | 1.0ms | 0.5ms | 2x
500 | 5.5ms | 1.2ms | 4.5x
1000 | 12ms | 2.0ms | 6x
Facts | Insertion | Query | Update (Incremental)
-------|-----------|-------|---------------------
100 | 0.4ms | 0.1µs | 0.1ms
1000 | 4.0ms | 0.1µs | 0.4ms
10000 | 45ms | 0.1µs | 4.0ms
// Bad: Native engine with 500+ rules
let engine = RustRuleEngine::new();
// Good: RETE engine for scalability
let engine = IncrementalEngine::new();// Automatic in RETE engine
let mut evaluator = MemoizedEvaluator::new();
// 99.99% cache hit rate for repeated patterns// Validation cost: 1-2µs (negligible)
let template = TemplateBuilder::new("Order")
.required_string("order_id")
.build();
engine.templates_mut().register(template);// Bad: Multiple single updates
engine.insert("Order", order1);
engine.fire_all(); // Expensive!
engine.insert("Order", order2);
engine.fire_all(); // Expensive!
// Good: Batch insert then fire
engine.insert("Order", order1);
engine.insert("Order", order2);
engine.fire_all(); // Once!// High salience for critical rules
rule "FraudCheck" salience 100 { ... }
// Low salience for logging
rule "AuditLog" salience 1 { ... }// Bad: Copying large facts
let large_fact = facts.clone(); // Expensive!
// Good: Use references
let value = facts.get("field"); // Cheap!Component | Memory | Notes
-------------------|--------|---------------------------
Working Memory | ~100B | Per fact (avg)
Templates | ~1KB | Per template definition
Globals | ~100B | Per global variable
Rules (compiled) | ~2KB | Per rule (avg)
Memoization Cache | ~500B | Per cached evaluation
Dependency Graph | ~200B | Per rule-fact relationship
// 1. Clear memoization cache periodically
evaluator.clear_cache();
// 2. Retract unused facts
engine.retract(old_fact_handle)?;
// 3. Use globals sparingly
// Globals persist across firings
engine.globals().remove("temp_var")?;# Run benchmarks
cargo bench
# Generate flame graphs
cargo flamegraph --bench rule_execution
# Profile memory
cargo instruments -t Allocations --bench rule_executionuse std::time::Instant;
let start = Instant::now();
engine.fire_all();
let duration = start.elapsed();
println!("Execution took: {:?}", duration);
println!("Per rule: {:?}", duration / rules.len() as u32);- Use RETE-UL with memoization
- Batch fact updates
- Enable incremental propagation
- Monitor cache hit rates
- Use Native engine for small rule sets
- Minimize fact copies
- Use direct fact access
- Avoid complex patterns
- Clear memoization cache regularly
- Use templates sparingly
- Retract unused facts
- Monitor memory usage
use std::time::Instant;
use rust_rule_engine::rete::IncrementalEngine;
fn benchmark_engine() {
let mut engine = IncrementalEngine::new();
// Load your rules
// ...
// Warm-up run
engine.fire_all();
// Benchmark
let iterations = 1000;
let start = Instant::now();
for _ in 0..iterations {
engine.reset();
engine.fire_all();
}
let duration = start.elapsed();
let avg = duration / iterations;
println!("Average execution: {:?}", avg);
println!("Throughput: {} rules/sec",
1_000_000_000 / avg.as_nanos());
}-
Rule Execution Time
- Average per rule
- P50, P95, P99 latencies
- Slowest rules
-
Memory Usage
- Working memory size
- Cache size
- Total heap usage
-
Cache Efficiency
- Hit rate percentage
- Miss rate
- Cache size vs. performance
-
Throughput
- Rules fired per second
- Facts processed per second
- Updates per second
Last Updated: 2025-10-31 (v0.10.0) Benchmarked On:
- CPU: Intel i7-11th Gen
- RAM: 16GB
- OS: Linux 6.8.0
- Rust: 1.75.0