Skip to content

Improve interop performance against dictionary #2088

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 2 commits into from
Apr 4, 2025

Conversation

lahma
Copy link
Collaborator

@lahma lahma commented Apr 3, 2025

I'm adding the test case and also the option which shows converting to native JsValue objects from JSON deserialization. If use case is reading then native types should produce best performance. I might have made mistakes so take it with a grain of salt.

The delegate building is now cached against AST node reference so it should align quite well with the idea of preparing and caching AST when needed.

Jint.Benchmark.InteropLambdaBenchmark

Diff Type Method Mean Error Allocated
Old ClrObject InlineCSharp 13.958 μs 0.1449 μs 30.08 KB
New 12.110 μs (-13%) 0.2307 μs 28.2 KB (-6%)
Old ClrObject InlineEngineInvoke 14.468 μs 0.1819 μs 30 KB
New 12.620 μs (-13%) 0.1171 μs 28.13 KB (-6%)
Old ClrObject Inline 14.810 μs 0.1906 μs 29.61 KB
New 12.871 μs (-13%) 0.1735 μs 27.73 KB (-6%)
Old ClrObject ForLoopEngineInvoke 20.211 μs 0.1940 μs 34.92 KB
New 18.729 μs (-7%) 0.3184 μs 32.66 KB (-6%)
Old ClrObject ForLoop 20.554 μs 0.3373 μs 34.53 KB
New 18.925 μs (-8%) 0.3124 μs 33.05 KB (-4%)
Old JsonNode ForLoop 49.571 μs 0.7526 μs 60.63 KB
New 34.749 μs (-30%) 0.6410 μs 47.97 KB (-21%)
Old JsonNode ForLoopEngineInvoke 50.650 μs 0.6915 μs 61.02 KB
New 34.759 μs (-31%) 0.6888 μs 47.58 KB (-22%)
Old JsonNode Inline 56.960 μs 1.1315 μs 75.87 KB
New 41.791 μs (-27%) 0.8285 μs 62.82 KB (-17%)
Old JsonNode InlineCSharp 61.993 μs 1.2329 μs 76.34 KB
New 41.995 μs (-32%) 0.8274 μs 63.29 KB (-17%)
Old JsonNode InlineEngineInvoke 62.023 μs 1.2133 μs 76.26 KB
New 42.142 μs (-32%) 0.8274 μs 63.21 KB (-17%)
Old Dictionary ForLoop 37.053 μs 0.3455 μs 47.11 KB
New 22.510 μs (-39%) 0.2309 μs 42.58 KB (-10%)
Old Dictionary ForLoopEngineInvoke 37.151 μs 0.4640 μs 47.5 KB
New 23.255 μs (-37%) 0.2261 μs 33.13 KB (-30%)
Old Dictionary Inline 2,421.450 μs 31.9202 μs 150.02 KB
New 23.602 μs (-99%) 0.4647 μs 42.5 KB (-72%)
Old Dictionary InlineEngineInvoke 2,426.210 μs 42.1312 μs 150.42 KB
New 23.608 μs (-99%) 0.3058 μs 42.11 KB (-72%)
Old Dictionary InlineCSharp 2,439.785 μs 47.8304 μs 150.5 KB
New 23.728 μs (-99%) 0.3679 μs 33.52 KB (-78%)
Old JsValue InlineCSharp 8.711 μs 0.1299 μs 18.91 KB
New 8.943 μs (+3%) 0.1251 μs 18.91 KB (0%)
Old JsValue InlineEngineInvoke 9.205 μs 0.1786 μs 18.83 KB
New 9.063 μs (-2%) 0.0782 μs 18.83 KB (0%)
Old JsValue Inline 9.278 μs 0.1288 μs 18.44 KB
New 9.175 μs (-1%) 0.1086 μs 18.44 KB (0%)
Old JsValue ForLoopEngineInvoke 14.978 μs 0.1502 μs 23.75 KB
New 14.975 μs (0%) 0.1385 μs 23.75 KB (0%)
Old JsValue ForLoop 15.345 μs 0.2494 μs 23.36 KB
New 15.260 μs (-1%) 0.1876 μs 23.36 KB (0%)

fixes #2087

Copy link

@Copilot Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull Request Overview

This pull request improves interop performance when working with dictionaries and native JsValue conversions and updates the caching mechanisms involved in array-like wrappers, delegate conversion, and operator overload method lookups. The changes include refactoring the array-like wrapper resolution for performance, updating delegate caching in type conversion, modifying function construction and object conversion, and adding caching for extension and operator overload methods.

Reviewed Changes

Copilot reviewed 5 out of 5 changed files in this pull request and generated no comments.

File Description
Jint/Runtime/Interop/ObjectWrapper.cs Refactored array-like wrapper resolution using a ConcurrentDictionary and optimized property access.
Jint/Runtime/Interop/DefaultTypeConverter.cs Updated delegate caching mechanism with ConditionalWeakTable and improved array conversion check.
Jint/Native/Function/Function.cs Simplified constructor parameters and added a ToObject override to return the delegate directly.
Jint/Extensions/ReflectionExtensions.cs Introduced caching for operator overload method lookups to reduce reflection overhead.
Comments suppressed due to low confidence (1)

Jint/Runtime/Interop/DefaultTypeConverter.cs:135

  • [nitpick] The variable name 'astFunction' could be more descriptive (e.g., 'functionDefinition') to clarify that it represents the underlying function definition for delegate caching.
var astFunction = (func.Target as Function)?._functionDefinition?.Function;

@lahma lahma force-pushed the interop-perf-improvements branch from 395384b to 9f46d98 Compare April 3, 2025 18:54
@sebastienros
Copy link
Owner

I want to see the issue updated with the new results now.

@lahma
Copy link
Collaborator Author

lahma commented Apr 3, 2025

I want to see the issue updated with the new results now.

I copied the benchmarks and modified to support running as single (params for scenario) with addition of projecting to JsValue data (ideal). So I would guess this reflects the original problem.

@lahma
Copy link
Collaborator Author

lahma commented Apr 3, 2025

The codebase is mostly benchmarked against JS scenarios so these interop ones can be tricky. You could probably start a consulting business about the performance traps with interop and JS...

@lahma
Copy link
Collaborator Author

lahma commented Apr 4, 2025

Here's the full non-compared benchmark result:


BenchmarkDotNet v0.14.0, Windows 11 (10.0.26100.3624)
12th Gen Intel Core i9-12900H, 1 CPU, 20 logical and 14 physical cores
.NET SDK 9.0.202
  [Host]     : .NET 9.0.3 (9.0.325.11113), X64 RyuJIT AVX2
  DefaultJob : .NET 9.0.3 (9.0.325.11113), X64 RyuJIT AVX2


Method Type Mean Error StdDev Ratio RatioSD Rank Gen0 Gen1 Allocated Alloc Ratio
InlineCSharp ClrObject 12.110 μs 0.2307 μs 0.2265 μs 0.65 0.02 1 2.2888 0.0153 28.2 KB 0.86
InlineEngineInvoke ClrObject 12.620 μs 0.1171 μs 0.1096 μs 0.67 0.01 1 2.2888 0.0153 28.13 KB 0.86
Inline ClrObject 12.871 μs 0.1735 μs 0.1449 μs 0.69 0.01 1 2.2583 0.0153 27.73 KB 0.85
ForLoop ClrObject 18.729 μs 0.3184 μs 0.2978 μs 1.00 0.02 2 2.6550 - 32.66 KB 1.00
ForLoopEngineInvoke ClrObject 18.925 μs 0.3124 μs 0.2922 μs 1.01 0.02 2 2.6855 - 33.05 KB 1.01
ForLoopEngineInvoke JsonNode 34.749 μs 0.6410 μs 0.5996 μs 1.00 0.02 1 3.9063 - 47.97 KB 1.01
ForLoop JsonNode 34.759 μs 0.6888 μs 0.6443 μs 1.00 0.03 1 3.8452 - 47.58 KB 1.00
Inline JsonNode 41.791 μs 0.8285 μs 1.2899 μs 1.20 0.04 2 4.8828 - 62.82 KB 1.32
InlineCSharp JsonNode 41.995 μs 0.8274 μs 0.7740 μs 1.21 0.03 2 4.8828 - 63.29 KB 1.33
InlineEngineInvoke JsonNode 42.142 μs 0.8274 μs 1.2128 μs 1.21 0.04 2 4.8828 - 63.21 KB 1.33
InlineCSharp Dictionary 22.510 μs 0.2309 μs 0.2047 μs 0.97 0.01 1 3.4485 0.0305 42.58 KB 1.29
ForLoop Dictionary 23.255 μs 0.2261 μs 0.2005 μs 1.00 0.01 1 2.6855 - 33.13 KB 1.00
InlineEngineInvoke Dictionary 23.602 μs 0.4647 μs 0.4120 μs 1.01 0.02 1 3.4485 0.0305 42.5 KB 1.28
Inline Dictionary 23.608 μs 0.3058 μs 0.2861 μs 1.02 0.01 1 3.4180 0.0305 42.11 KB 1.27
ForLoopEngineInvoke Dictionary 23.728 μs 0.3679 μs 0.3441 μs 1.02 0.02 1 2.7161 - 33.52 KB 1.01
InlineCSharp JsValue 8.943 μs 0.1251 μs 0.1045 μs 0.59 0.01 1 1.5411 - 18.91 KB 0.81
InlineEngineInvoke JsValue 9.063 μs 0.0782 μs 0.0694 μs 0.59 0.01 1 1.5259 - 18.83 KB 0.81
Inline JsValue 9.175 μs 0.1086 μs 0.0963 μs 0.60 0.01 1 1.4954 - 18.44 KB 0.79
ForLoopEngineInvoke JsValue 14.975 μs 0.1385 μs 0.1296 μs 0.98 0.01 2 1.9379 - 23.75 KB 1.02
ForLoop JsValue 15.260 μs 0.1876 μs 0.1755 μs 1.00 0.02 2 1.8921 - 23.36 KB 1.00

@lahma lahma merged commit e4800fa into sebastienros:main Apr 4, 2025
4 checks passed
@lahma lahma deleted the interop-perf-improvements branch April 4, 2025 06:36
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.

Using Dictionary<string, object> data with a lambdas drops execution performance by factor 50
2 participants