Skip to content

perf: share one ExpressionRuntime across DataFactoryElement evaluations - #182

Open
Florian BURELLI (florian-burelli) wants to merge 1 commit into
microsoft:mainfrom
florian-burelli:perf/shared-expression-runtime
Open

Florian BURELLI (florian-burelli) wants to merge 1 commit into
microsoft:mainfrom
florian-burelli:perf/shared-expression-runtime

Conversation

@florian-burelli

Copy link
Copy Markdown

Fixes #178

Problem

DataFactoryElement.evaluate builds a new ExpressionRuntime on every call, and ExpressionRuntime.__init__ builds an ExpressionTransformer, which compiles the Lark grammar of the expression language again. On this machine the compile costs about 50 ms, roughly four times the evaluation itself, so suites with many expressions spend most of their time rebuilding the same parser.

Change

  • _data_factory_element.py: a module-level _get_expression_runtime() behind functools.lru_cache builds the runtime once, lazily, and evaluate reuses it. The runtime holds no per-evaluation state: the transformer only keeps the compiled parser (DataFactoryExpressionTransformer and Reconstructor are still created per call), and the evaluator is a stateless facade over the .NET Evaluator.
  • ExpressionRuntime stays importable from _data_factory_element, and the factory looks it up at call time, so code that patches it there keeps working.

Numbers

before after
DataFactoryElement.evaluate, @concat('a', 'b'), mean of 30 65 ms 12 ms
this repository's test suite 18 s 12 s

The remaining suite time is dominated by the ~25 tests that construct their own ExpressionRuntime(); left untouched here.

Tests

  • test_evaluate_datafactory_element.py: evaluating several elements constructs ExpressionRuntime once and still returns the right results.

Full suite: 222 passed. Ruff check and format clean.

🤖 Generated with Claude Code

`DataFactoryElement.evaluate` built a new `ExpressionRuntime` on every
call, and each one compiles the Lark grammar of the expression language
again. The compile costs about 50 ms, roughly four times the evaluation
itself, so a test suite spent most of its time rebuilding the same parser.

Build the runtime once, lazily, and reuse it. It holds no per-evaluation
state: the transformer only keeps the compiled parser, and the evaluator
is a stateless facade over the .NET evaluator.

Measured on one expression (`@concat('a', 'b')`), 30 iterations:
before 65 ms per evaluation, after 12 ms. The test suite of this
repository goes from 18 s to 12 s.

Fixes microsoft#178

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
@florian-burelli

Copy link
Copy Markdown
Author

@microsoft-github-policy-service agree company="AWARE"

This branch has not been deployed

No deployments
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.

A new ExpressionRuntime (Lark grammar compile) per evaluated expression makes evaluation ~5x slower

1 participant