Competitive benchmark report

Generated 2026-06-24. A reproducible head-to-head of feelc against six other rule engines on one identical decision, run on a single Apple Silicon (darwin/arm64) host. This complements the feature matrix in comparison.md — that document compares capabilities; this one compares measured throughput on the same workload.

Every engine — Node, Go, Rust (via binding), and two JVM DMN engines — was cloned/installed at a released version, given the same 6-row priority-cascade decision and the same 200,000 deterministic input rows, warmed up identically, and verified against a shared correctness checksum (2,663,397). They all compute the same answers; they differ only in paradigm and binding cost.

The honest one-line takeaway: a bare tree-walking JSON interpreter (json-logic-js) wins raw µs on this trivial workload, but feelc's native compiled-table path is the second-fastest measured and the fastest engine that also provides in-engine static verification, exact decimals, deterministic evaluation, and a cgo-free single binary + portable WASM — a small constant factor traded for correctness guarantees the faster interpreters do not offer.


Discount-Decision Benchmark: feelc vs. Other Rule Engines

A single, identical decision was implemented in every engine: a 6-row priority cascade (amount>=500 & member → 20; amount>=500 → 15; amount>=100 & member → 12; amount>=100 → 10; amount>=50 → 5; else 0), evaluated FIRST-match. The same 200,000 deterministically generated rows (amount=(i*7)%1001, member=(i%2==0)) were run through each engine after a 10,000-eval untimed warmup, single-threaded, on the same Apple Silicon (darwin/arm64) host. Every engine produced the identical correctness checksum (sum of discounts = 2,663,397), so all rows below are computing the same answers — they differ only in paradigm and binding cost.

Comparison table

Engine Language / runtime eval/sec µs/op Startup Paradigm Notes
feelc (native) Go (native, EvalTable) ~1,550,000 ~0.65 ~0 Compiled decision table Reference; no binding boundary.
feelc (WASM batch) Go→WASM (Node 24, evaluateBatch) ~144,000 ~6.95 n/a Compiled table, batched JSON boundary 2.1× the per-call WASM path; amortizes the JS↔WASM crossing.
feelc (WASM per-call) Go→WASM (Node 24, evaluate) ~68,000 ~14.6 n/a Compiled table, per-call JSON boundary Per-op cost dominated by the JSON boundary, not the rule.
json-logic-js 2.0.5 JavaScript (Node 24.10.0) 2,721,522 0.367 ~0.017* Tree-walking JSON interpreter Fastest measured, but apples-to-oranges: re-interprets the rule tree every call; "startup" is a meaningless JSON round-trip (no compile step). Zero deps, 92K on disk.
GoRules ZEN 0.54.0 Rust core via Node napi (prebuilt) ~25,000 (seq) / ~170,000 (batched) ~40 (seq) / ~5.8 (batched) ~3 Compiled JDM graph (decision table + expr VM) Async napi binding only; no sync evaluate. Sequential await-per-row is round-trip-bound (high variance, 20.8k–29.6k eps); concurrent Promise.all batches reveal the Rust core is materially faster. Native first hit policy.
json-rules-engine 7.3.1 JavaScript (Node 24.10.0) 71,483 13.99 0.26 Async forward condition-eval (almanac) await engine.run() per op — microtask round-trip dominates. Evaluates ALL rules every run (no short-circuit); FIRST emulated via priority + events[0]. 3-run range 68.7k–74.0k eps.
Drools DMN 9.44.0 Java 21 (HotSpot, Docker) 328,022 3.05 212 DMN 1.3 decision table (FEEL, decimal) Warm-JIT steady state; cold one-shot far worse. Decimal BigDecimal like feelc; fresh DMNContext+DMNResult alloc per row. 3-run range 306.9k–330.3k eps.
grule 1.20.4 Go (native, go1.26) 525,219 1.904 1.254 Forward-chaining production engine (RETE-style) Interprets an ANTLR AST per eval; FIRST emulated via salience + Complete(). Per-op cost is largely reflection + working-memory reset, not rule logic. 2-run spread ~0.05%.
Camunda DMN 7.21.0 Java 21 (HotSpot, Docker) 4,124 242.5 173 DMN 1.3 table, Scala FEEL interpreter (decimal) Re-parses FEEL unary-test strings (">= 500") per cell per row; warm-JIT. Harmless DMN-01006 (untyped number) disables the typed fast-path. 2-run range 4,116–4,124 eps.

* json-logic-js has no compile step; its "startup" figure is a JSON round-trip added only so there was something to time, and is not comparable to engines that build a model.

Reading the numbers

These results compare different paradigms doing the same job, and the per-op figure is frequently dominated by binding and scheduling overhead rather than rule evaluation — so the ranking is not a clean "which engine is fastest" verdict. Concretely:

Engines not measured: none in this round failed to build — all six competitors plus feelc's three paths produced the correct checksum and are reported above. (Variance and methodology caveats are stated per row rather than hidden; where an engine had run-to-run spread, the median run is reported and the full range is shown.) Commercial/closed engines in the feature matrix (Microsoft RulesEngine [.NET], OpenRules, DecisionRules) were not throughput-benchmarked here.

Methodology & reproducibility

Identical decision logic, identical 200,000-row deterministic input (amount=(i*7)%1001, member=(i%2==0)), 10,000-eval untimed warmup, single-threaded timed loop, same Apple Silicon host; each engine pinned to a released version, verified against the shared checksum 2,663,397, and run 2–3× with median reported and ranges disclosed — numbers are machine-dependent and indicative of order-of-magnitude, not a tuned JMH/cross-host benchmark. feelc's own rows come from go test -bench (native) and packages/engine/scripts/bench-batch.mjs (WASM per-call + batch).