Skip to content
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
157 changes: 157 additions & 0 deletions Jint.Benchmark/ProxyBenchmark.cs
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
#nullable enable

using BenchmarkDotNet.Attributes;
using Jint.Native;
using Jint.Native.Object;
using Jint.Runtime.Interop;

namespace Jint.Benchmark;

Expand All @@ -8,6 +12,8 @@ namespace Jint.Benchmark;
/// (forwarding) handler, apply/construct on a function proxy, revocable creation + revoke
/// churn, and typeof on a revoked function proxy (which must not throw). Rows measure the
/// per-operation trap machinery — handler lookup, trap invocation and invariant checks.
/// The Clr* rows mirror the JS-handler rows 1:1 using the .NET <see cref="ProxyHandler"/>
/// trap API (Engine.Advanced.CreateProxy) over the same target objects.
/// </summary>
[MemoryDiagnoser]
[HideColumns("Error", "Gen0", "Gen1", "Gen2")]
Expand All @@ -26,6 +32,12 @@ public class ProxyBenchmark
private Prepared<Script> _constructTrap;
private Prepared<Script> _revocableCreate;
private Prepared<Script> _revokedTypeof;
private Prepared<Script> _clrTrapGet;
private Prepared<Script> _clrTrapSet;
private Prepared<Script> _clrTrapHas;
private Prepared<Script> _clrForwardGet;
private Prepared<Script> _clrApplyTrap;
private Prepared<Script> _clrConstructTrap;

private const string SetupSource = """
var target = { x: 1, a: 2, b: 3, c: 4, d: 5, e: 6, f: 7, g: 8, h: 9, k: 10 };
Expand All @@ -52,6 +64,13 @@ public void Setup()
_engine = new Engine();
_engine.Execute(SetupSource);

// CLR-handler proxies over the same targets as the JS-handler lanes
var target = (ObjectInstance) _engine.GetValue("target");
var fnTarget = (ObjectInstance) _engine.GetValue("fnTarget");
_engine.SetValue("pClrTrap", _engine.Advanced.CreateProxy(target, new TrappingClrHandler()));
_engine.SetValue("pClrForward", _engine.Advanced.CreateProxy(target, new ForwardingClrHandler()));
_engine.SetValue("fClrTrap", _engine.Advanced.CreateProxy(fnTarget, new ApplyClrHandler()));

_trapGet = Engine.PrepareScript("""
function f() {
var s = 0;
Expand Down Expand Up @@ -187,6 +206,71 @@ function f() {
f();
""");

_clrTrapGet = Engine.PrepareScript("""
function f() {
var s = 0;
for (var i = 0; i < 100000; i++) {
s += pClrTrap.x;
}
return s;
}
f();
""");

_clrTrapSet = Engine.PrepareScript("""
function f() {
for (var i = 0; i < 100000; i++) {
pClrTrap.x = i;
}
return pClrTrap.x;
}
f();
""");

_clrTrapHas = Engine.PrepareScript("""
function f() {
var s = 0;
for (var i = 0; i < 100000; i++) {
if ('x' in pClrTrap) { s++; }
}
return s;
}
f();
""");

_clrForwardGet = Engine.PrepareScript("""
function f() {
var s = 0;
for (var i = 0; i < 100000; i++) {
s += pClrForward.x;
}
return s;
}
f();
""");

_clrApplyTrap = Engine.PrepareScript("""
function f() {
var s = 0;
for (var i = 0; i < 100000; i++) {
s += fClrTrap(i, 1);
}
return s;
}
f();
""");

_clrConstructTrap = Engine.PrepareScript("""
function f() {
var s = 0;
for (var i = 0; i < 10000; i++) {
s += new fClrTrap(i, 1).v;
}
return s;
}
f();
""");

_engine.Evaluate(_trapGet);
_engine.Evaluate(_trapSet);
_engine.Evaluate(_trapHas);
Expand All @@ -199,6 +283,23 @@ function f() {
_engine.Evaluate(_constructTrap);
_engine.Evaluate(_revocableCreate);
_engine.Evaluate(_revokedTypeof);

// warm the CLR lanes and sanity-check the trap wiring; ClrTrapSet runs first so
// target.x is a known 99999 before the get lanes read it
AssertResult(_engine.Evaluate(_clrTrapSet), 99_999, nameof(ClrTrapSet));
AssertResult(_engine.Evaluate(_clrTrapGet), 9_999_900_000, nameof(ClrTrapGet));
AssertResult(_engine.Evaluate(_clrTrapHas), 100_000, nameof(ClrTrapHas));
AssertResult(_engine.Evaluate(_clrForwardGet), 9_999_900_000, nameof(ClrForwardGet));
AssertResult(_engine.Evaluate(_clrApplyTrap), 5_000_050_000, nameof(ClrApplyTrap));
AssertResult(_engine.Evaluate(_clrConstructTrap), 49_995_000, nameof(ClrConstructTrap));
}

private static void AssertResult(JsValue actual, double expected, string lane)
{
if (!actual.IsNumber() || actual.AsNumber() != expected)
{
throw new InvalidOperationException($"{lane} returned {actual}, expected {expected}");
}
}

[Benchmark]
Expand Down Expand Up @@ -236,4 +337,60 @@ function f() {

[Benchmark]
public JsValue RevokedTypeof() => _engine.Evaluate(_revokedTypeof);

[Benchmark]
public JsValue ClrTrapGet() => _engine.Evaluate(_clrTrapGet);

[Benchmark]
public JsValue ClrTrapSet() => _engine.Evaluate(_clrTrapSet);

[Benchmark]
public JsValue ClrTrapHas() => _engine.Evaluate(_clrTrapHas);

[Benchmark]
public JsValue ClrForwardGet() => _engine.Evaluate(_clrForwardGet);

[Benchmark]
public JsValue ClrApplyTrap() => _engine.Evaluate(_clrApplyTrap);

[Benchmark]
public JsValue ClrConstructTrap() => _engine.Evaluate(_clrConstructTrap);

/// <summary>
/// CLR equivalent of the JS handler <c>{ get: (t, k) => t[k], set: (t, k, v) => (t[k] = v, true), has: (t, k) => k in t }</c>.
/// </summary>
private sealed class TrappingClrHandler : ProxyHandler
{
public override JsValue? Get(ObjectInstance target, JsValue property, JsValue receiver) => target.Get(property, receiver);

public override bool? Set(ObjectInstance target, JsValue property, JsValue value, JsValue receiver)
{
target.Set(property, value);
return true;
}

public override bool? Has(ObjectInstance target, JsValue property) => target.HasProperty(property);
}

/// <summary>
/// CLR equivalent of the empty JS handler <c>{}</c>: every trap forwards to the target.
/// </summary>
private sealed class ForwardingClrHandler : ProxyHandler
{
}

/// <summary>
/// CLR equivalent of the JS handler <c>{ apply: (t, self, args) => t(args[0], args[1]), construct: (t, args) => ({ v: args[0] }) }</c>.
/// </summary>
private sealed class ApplyClrHandler : ProxyHandler
{
public override JsValue? Apply(ObjectInstance target, JsValue thisObject, JsValue[] arguments) => target.Engine.Call(target, thisObject, arguments);

public override ObjectInstance? Construct(ObjectInstance target, JsValue[] arguments, JsValue newTarget)
{
var result = new JsObject(target.Engine);
result.Set("v", arguments[0]);
return result;
}
}
}
Loading