perf(graaljs): opt-in per-thread context reuse, plus benchmark tooling and engine guide - #53
Merged
Merged
Conversation
Added engine comparison table, internals explanations, trade-offs, and performance data. Added bench Gradle tasks.
Introduces `reuseContextPerThread` flag to keep one context per thread instead of building and closing per evaluation. Context construction dominated runtime; reusing it removes that cost. Maintains thread-safety and input isolation by injecting a fresh object per call. Adds benchmark comparison, comprehensive docs on trade-offs, and concurrent evaluation tests.
…ntext * origin/main: docs: add benchmark comparison and engine guide (#52) # Conflicts: # README.md # graaljs-evaluator/build.gradle
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What
Two related changes to the GraalJS evaluator and the docs around it.
Benchmark tooling and engine guide (
edb7589)Adds a
benchtask to each evaluator module that replays the 147 expressions fromTestDataagainst its engine, and documents the three engines in the README:how each one works, what it is best for, its trade-offs, and where its time goes.
Opt-in context reuse (
6413b0c)GraalJSEvaluator.callbuilt a new polyglotContextfor everyevaluatecall andnever closed it. Context construction was about 88% of a single-rule evaluation, and
the contexts were left to the garbage collector rather than released deterministically.
use { }, so it is closed on every path.reuseContextPerThreadflag (defaultfalse, so existing behaviour is unchanged).When enabled, each thread keeps one
Contextin aThreadLocal. A polyglotContextrejects concurrent access, so one context per thread keeps
evaluatesafe to callconcurrently without a lock that would serialise evaluations.
object bound to a single global (
__ruleset_input__) that is replaced wholesale, andoperators evaluate inside
with (__ruleset_input__) { ... }. Binding isolation isstructural in both modes instead of depending on a fresh context.
evaluatethroughEvalContext.engine(). In the reused modethe nested call would otherwise leave its own input in place for the rest of the outer
evaluation, so the enclosing input scope is saved and restored.
Numbers
2000 iterations of the 147-expression suite, Apple M3 Pro, Amazon Corretto 21.
reuseContextPerThread = trueReuse is roughly 25x. Its spread across runs is wide: with the context cost gone, an
iteration is short enough that this timing loop measures JIT and GC noise as much as the
engine. Closing the context and the namespace indirection cost the default path about 4%,
and buy deterministic release plus isolation that holds under reuse.
Behaviour notes
Valuereturned outof
callis dead once it returns;evaluatereturns aBoolean, so nothing in-tree isaffected.
parseParameterskeeps its signature but now receives the per-evaluation scope objectrather than the global bindings. Subclasses that override it are unaffected in behaviour.
globalThis.x = 1, redefininga builtin); those persist for later evaluations on the same thread. Per-thread contexts
are not closed either, so it suits a bounded pool. Both are documented in the README.
Verification
./gradlew checkis green: detekt, every module's tests, and the kover 90% branch andinstruction gates.
GraalJSReusedContextEvaluatorTestruns the full case suite in reused mode (175 tests)plus targeted tests for stale bindings between evaluations, nested evaluation through
engine(), and 8-thread concurrent evaluation.guards the behaviour rather than passing incidentally.
./gradlew :graaljs-evaluator:bench -PbenchIterations=2000 [-PbenchReuse=true]for thefigures above.
Rhino's equivalent scope-per-call cost is untouched and left as separate work.