Skip to content
Merged
Show file tree
Hide file tree
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
2 changes: 1 addition & 1 deletion dotnet-bridge-benchmarks/BridgeBenchmarks.cs
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
using BenchmarkDotNet.Running;
using NativeScriptBridge;

BenchmarkSwitcher.FromTypes([typeof(BridgeBenchmarks), typeof(AbiBenchmarks)]).Run(args);
BenchmarkSwitcher.FromTypes([typeof(BridgeBenchmarks), typeof(AbiBenchmarks), typeof(CallbackBenchmarks), typeof(EntryBenchmarks)]).Run(args);

/// <summary>
/// Measures the hot-path dispatch cost for the JSON and binary protocols.
Expand Down
69 changes: 69 additions & 0 deletions dotnet-bridge-benchmarks/CallbackBenchmarks.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
using System;
using System.Buffers;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using BenchmarkDotNet.Attributes;
using NativeScriptBridge;

public class CallbackBase
{
public virtual int Compute(int value) => value;

public int Run(int value) => Compute(value);
}

/// <summary>
/// Managed cost of .NET calling into JS: a JS function passed as a delegate, and a JS subclass
/// override called from C#. The "JS side" is an unmanaged function that answers at once, so
/// what's measured is the bridge's own work (argument encoding, response handling).
///
/// dotnet run -c Release -- --filter *CallbackBenchmarks* --job short
/// </summary>
[MemoryDiagnoser]
public unsafe class CallbackBenchmarks
{
private Func<int, int> _func = null!;
private Action<string> _action = null!;
private CallbackBase _override = null!;

[UnmanagedCallersOnly(CallConvs = [typeof(CallConvCdecl)])]
private static void FakeJs(int id, byte* args, int len, byte** resp, int* respLen)
{
// Every callback answers with the int 42, allocated the way the runtime allocates it.
var p = (byte*)Marshal.AllocHGlobal(5);
p[0] = 0x03;
*(int*)(p + 1) = 42;
*resp = p;
*respLen = 5;
}

[GlobalSetup]
public void Setup()
{
Bridge.ClearCaches();
Bridge.s_jsInvoker = &FakeJs;
_func = (Func<int, int>)Bridge.MakeJsDelegate(typeof(Func<int, int>), 1);
_action = (Action<string>)Bridge.MakeJsDelegate(typeof(Action<string>), 2);

var buf = new ArrayBufferWriter<byte>(64);
var w = new BinWriter(buf);
w.WriteByte(0x0A);
w.WriteString16("");
w.WriteString16(typeof(CallbackBase).FullName!);
w.WriteI32(0);
w.WriteI32(1);
w.WriteString16("Compute");
w.WriteI32(3);
w.WriteByte(0);
var r = new BinReader(buf.WrittenSpan);
var handle = Bridge.DispatchBin(ref r).HandleId();
_override = (CallbackBase)Bridge.s_handles[handle]!;
if (_override.Run(1) != 42) throw new InvalidOperationException("override did not reach the fake JS side");
}

[Benchmark] public int DelegateWithResult() => _func(5);

[Benchmark] public void VoidDelegate() => _action("hello");

[Benchmark] public int OverrideFromCSharp() => _override.Run(5);
}
36 changes: 36 additions & 0 deletions dotnet-bridge-benchmarks/EntryBenchmarks.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
using System;
using System.Reflection;
using BenchmarkDotNet.Attributes;
using NativeScriptBridge;

/// <summary>
/// What a method costs the first time JS calls it (building its invoker) and after that.
///
/// dotnet run -c Release -- --filter *EntryBenchmarks* --job short
/// </summary>
[MemoryDiagnoser]
public class EntryBenchmarks
{
private static readonly MethodInfo s_max = typeof(Math).GetMethod(nameof(Math.Max), [typeof(int), typeof(int)])!;
private static readonly MethodInfo s_append = typeof(System.Text.StringBuilder).GetMethod(nameof(System.Text.StringBuilder.Append), [typeof(string)])!;
private readonly object?[] _maxArgs = [3, 7];
private readonly System.Text.StringBuilder _sb = new();
private readonly object?[] _appendArgs = [""];
private DispatchEntry _warmMax;
private DispatchEntry _warmAppend;

[GlobalSetup]
public void Setup()
{
_warmMax = Bridge.BuildDispatchEntry(typeof(Math), s_max);
_warmAppend = Bridge.BuildDispatchEntry(typeof(System.Text.StringBuilder), s_append);
}

[Benchmark] public object? FirstCallStatic() => Bridge.BuildDispatchEntry(typeof(Math), s_max).Invoke!(null, _maxArgs);

[Benchmark] public object? FirstCallInstance() => Bridge.BuildDispatchEntry(typeof(System.Text.StringBuilder), s_append).Invoke!(_sb, _appendArgs);

[Benchmark] public object? WarmStatic() => _warmMax.Invoke!(null, _maxArgs);

[Benchmark] public object? WarmInstance() => _warmAppend.Invoke!(_sb, _appendArgs);
}
Loading
Loading