Skip to content

perf: binding variable conversion (python and nodejs)#474

Merged
stefan-gorules merged 1 commit into
masterfrom
perf/binding-variable-conversion
Jun 29, 2026
Merged

perf: binding variable conversion (python and nodejs)#474
stefan-gorules merged 1 commit into
masterfrom
perf/binding-variable-conversion

Conversation

@stefan-gorules

@stefan-gorules stefan-gorules commented Jun 26, 2026

Copy link
Copy Markdown
Contributor

Faster Node & Python bindings: direct Variable to native conversion

Replaces the Variable -> serde_json::Value -> JsValue/PyObject round-trip with a direct, Rc-deduplicated conversion on every engine/decision eval-output path. Shared sub-trees (huge in traces, where node N's output is node N+1's input) are materialized once and reused.

Results (vs Rust floor; same machine)

fixture node python
expression-table-map +trace 18.6ms -> 3.1ms (6.0x) 11.1ms -> 2.1ms (5.4x)
dynamic-shipping +trace 29.2ms -> 4.4ms (6.6x) 16.9ms -> 2.6ms (6.5x)
customer-eligibility +trace 21.4ms -> 2.6ms (8.3x) 12.7ms -> 1.7ms (7.4x)
heavy graphs (no trace) 1.3-1.5x 1.7-1.8x

What's in here

  • Direct converters for all eval paths: Node evaluate / safeEvaluate / evaluateBatch, Python sync + async evaluate (engine + decision).
  • Same-thread (Python sync) = direct Variable -> PyObject; cross-thread (Node, Python async) = Send dedup arena built on the worker, materialized with handle reuse on the JS/GIL thread (Variable is !Send).
  • New: Python evaluate_batch + async_evaluate_batch (parity with Node), parallel, returns [{success, data, error}].
  • New: Python CI test job (ubuntu+macos x py3.9/3.13); release now gated on it.

@stefan-gorules

Copy link
Copy Markdown
Contributor Author

Benchmark: before vs after (per-op, lower is better)

fixture rust node before -> after python before -> after
decision-table-discounts (graph) 64.2 µs 451.2 -> 459.8 µs (0.98x) 106.3 -> 125.1 µs (0.85x)
expression-table-map (graph) 2,600.5 µs 41,226.7 -> 6,892.9 µs (5.98x) 29,395.1 -> 4,209.2 µs (6.98x)
credit-limit-adjustment (graph) 209.3 µs 858.2 -> 695.2 µs (1.23x) 357.4 -> 283.9 µs (1.26x)
dynamic-shipping-cost-calculator (graph) 3,160.2 µs 61,447.8 -> 9,816.9 µs (6.26x) 42,850.5 -> 5,210.6 µs (8.22x)
customer-eligibility-engine (graph) 2,270.9 µs 45,938.7 -> 5,824.2 µs (7.89x) 25,776.0 -> 3,612.3 µs (7.14x)
simple-assertion (policy) 43.5 µs 359.2 -> 385.4 µs (0.93x) 99.8 -> 114.3 µs (0.87x)
expression (policy) 73.2 µs 412.7 -> 444.4 µs (0.93x) 139.4 -> 147.3 µs (0.95x)
multi-assertion (policy) 743.5 µs 1,766.6 -> 1,811.3 µs (0.98x) 1,255.9 -> 1,118.3 µs (1.12x)
expression-chain (policy) 653.5 µs 2,664.0 -> 2,730.4 µs (0.98x) 2,064.8 -> 1,653.5 µs (1.25x)
match (policy) 64.6 µs 390.1 -> 400.5 µs (0.97x) 124.9 -> 136.1 µs (0.92x)

Big graph fixtures: ~6-8x faster (node and python). Small policy fixtures are flat (within run noise) - they're dispatch-bound, not marshaling-bound.

@stefan-gorules stefan-gorules merged commit b6fac7c into master Jun 29, 2026
32 checks passed
@stefan-gorules stefan-gorules deleted the perf/binding-variable-conversion branch June 29, 2026 11:58
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.

2 participants