|
| 1 | +# Filter Chain Optimization |
| 2 | + |
| 3 | +## Overview |
| 4 | + |
| 5 | +This branch introduces a performance optimization for chained filter expressions in Jinjava's expression language. The optimization is **configurable** and **disabled by default** for backward compatibility. |
| 6 | + |
| 7 | +## Problem |
| 8 | + |
| 9 | +Previously, chained filters like `input|trim|lower|length` were parsed as deeply nested AST method calls: |
| 10 | + |
| 11 | +``` |
| 12 | +filter:length.filter(filter:lower.filter(filter:trim.filter(input, interpreter), interpreter), interpreter) |
| 13 | +``` |
| 14 | + |
| 15 | +This resulted in: |
| 16 | +- Multiple redundant filter lookups per AST node traversal |
| 17 | +- Increased method invocation overhead |
| 18 | +- Extra object wrapping/unwrapping between filters |
| 19 | +- Unnecessary context operations |
| 20 | + |
| 21 | +## Solution |
| 22 | + |
| 23 | +The optimization introduces a new `AstFilterChain` AST node that represents the entire filter chain as a **single evaluation unit**: |
| 24 | + |
| 25 | +``` |
| 26 | +input|trim|lower|length |
| 27 | +``` |
| 28 | + |
| 29 | +Instead of nested method calls, the filter chain is evaluated in a single pass: |
| 30 | +1. Look up each filter once |
| 31 | +2. Directly invoke `filter.filter(...)` sequentially |
| 32 | +3. Handle `SafeString` preservation inline |
| 33 | +4. Handle disabled filters and errors |
| 34 | + |
| 35 | +## New Files |
| 36 | + |
| 37 | +| File | Description | |
| 38 | +|------|-------------| |
| 39 | +| `AstFilterChain.java` | AST node for optimized filter chain evaluation | |
| 40 | +| `FilterSpec.java` | Data class holding filter name and parameters | |
| 41 | +| `AstFilterChainPerformanceTest.java` | Performance tests verifying the optimization | |
| 42 | + |
| 43 | +## Modified Files |
| 44 | + |
| 45 | +| File | Changes | |
| 46 | +|------|---------| |
| 47 | +| `JinjavaConfig.java` | Added `enableFilterChainOptimization` config option | |
| 48 | +| `ExtendedParser.java` | Added conditional logic to use optimization based on config | |
| 49 | +| `EagerExtendedParser.java` | Override to always use old behavior (no optimization for eager mode) | |
| 50 | + |
| 51 | +## Configuration |
| 52 | + |
| 53 | +To enable the filter chain optimization: |
| 54 | + |
| 55 | +```java |
| 56 | +JinjavaConfig config = JinjavaConfig.newBuilder() |
| 57 | + .withEnableFilterChainOptimization(true) |
| 58 | + .build(); |
| 59 | + |
| 60 | +Jinjava jinjava = new Jinjava(config); |
| 61 | +``` |
| 62 | + |
| 63 | +### Behavior by Execution Mode |
| 64 | + |
| 65 | +| Mode | Config Enabled | Behavior | |
| 66 | +|------|---------------|----------| |
| 67 | +| Normal (non-eager) | `true` | Uses optimized `AstFilterChain` | |
| 68 | +| Normal (non-eager) | `false` | Uses old nested method approach | |
| 69 | +| Eager | `true` or `false` | Always uses old nested method approach | |
| 70 | + |
| 71 | +**Note:** The optimization is disabled for eager execution mode regardless of configuration, as eager mode requires the old behavior for proper deferred value handling. |
| 72 | + |
| 73 | +## Performance |
| 74 | + |
| 75 | +The optimization provides measurable performance improvements for chained filter expressions: |
| 76 | + |
| 77 | +- **Single filter**: Minimal improvement |
| 78 | +- **2+ chained filters**: Noticeable improvement |
| 79 | +- **5+ chained filters**: Significant improvement |
| 80 | + |
| 81 | +Run the performance test to see actual numbers: |
| 82 | + |
| 83 | +```bash |
| 84 | +# Run automated performance test |
| 85 | +mvn test -Dtest=AstFilterChainPerformanceTest |
| 86 | + |
| 87 | +# Run detailed benchmark (main method) |
| 88 | +mvn exec:java -Dexec.mainClass="com.hubspot.jinjava.el.ext.AstFilterChainPerformanceTest" |
| 89 | +``` |
| 90 | + |
| 91 | +## Commits |
| 92 | + |
| 93 | +1. **Chained filters optimization** - Initial implementation with `AstFilterChain` and `FilterSpec` |
| 94 | +2. **Make filter chain optimization configurable and disable for eager mode** - Added config option and disabled for eager execution |
| 95 | +3. **Add performance test for filter chain optimization** - Added tests to verify correctness and performance |
| 96 | + |
| 97 | +## Backward Compatibility |
| 98 | + |
| 99 | +- The optimization is **disabled by default** (`enableFilterChainOptimization = false`) |
| 100 | +- Existing code works unchanged without any configuration |
| 101 | +- Eager execution mode always uses the old behavior |
| 102 | +- All existing tests pass without modification |
0 commit comments