From c54c1ada5ba1d8e6250648858822b9f10d8f3af8 Mon Sep 17 00:00:00 2001 From: Marko Lahma Date: Wed, 11 Mar 2026 21:34:18 +0200 Subject: [PATCH 1/2] Fix prepared script performance regression caused by shared JintIdentifierExpression cache thrashing When using Engine.PrepareScript(), the AstAnalyzer stored JintIdentifierExpression instances in AST node UserData. Since JintExpression.Build() returns these cached instances directly, ALL references to the same identifier across all scopes and all engine instances shared a single JintIdentifierExpression with its mutable _cachedEnvironment field. Different scopes continuously overwrote each other's cached environment, causing the fast-path lookup to always miss. The fix stores only the immutable BindingName in UserData instead. Each engine execution now creates fresh JintIdentifierExpression instances (with independent environment caches) while still reusing the pre-computed BindingName to avoid repeated JsString/BindingName allocation. This also fixes a minor bug in the single-parameter JintIdentifierExpression constructor that created two redundant BindingName instances (one in the this() chain, one in the body). Co-Authored-By: Claude Opus 4.6 --- Jint.Benchmark/PreparedScriptBenchmark.cs | 48 +++++++++++++++++++ Jint/Engine.Ast.cs | 2 +- .../Expressions/JintIdentifierExpression.cs | 10 ++-- 3 files changed, 52 insertions(+), 8 deletions(-) create mode 100644 Jint.Benchmark/PreparedScriptBenchmark.cs diff --git a/Jint.Benchmark/PreparedScriptBenchmark.cs b/Jint.Benchmark/PreparedScriptBenchmark.cs new file mode 100644 index 0000000000..ed550e155c --- /dev/null +++ b/Jint.Benchmark/PreparedScriptBenchmark.cs @@ -0,0 +1,48 @@ +using BenchmarkDotNet.Attributes; + +namespace Jint.Benchmark; + +/// +/// Tests prepared script reuse across multiple engine instances, +/// which is the pattern used by JavaScriptEngineSwitcher and similar hosts. +/// +[MemoryDiagnoser] +public class PreparedScriptBenchmark +{ + private string _script = null!; + private Prepared