未验证 提交 35704e44 编写于 作者: R Radek Doulik 提交者: GitHub

[wasm] Add AppStart task to the bench Sample (#61481)

Measure browser app start times, 2 measurements implemented.

First to measure till the JS window.pageshow event, second to measure
time when we reach managed C# code.

Example ouput:

    | measurement | time |
    |-:|-:|
    |                    AppStart, Page show |   108.1400ms |
    |                AppStart, Reach managed |   240.2174ms |
上级 5fa6dd36
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
using System;
using System.Diagnostics.CodeAnalysis;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Text.Json;
using System.Threading.Tasks;
namespace Sample
{
public class AppStartTask : BenchTask
{
public override string Name => "AppStart";
public override bool BrowserOnly => true;
[DynamicDependency(DynamicallyAccessedMemberTypes.NonPublicConstructors, "System.Runtime.InteropServices.JavaScript.Runtime", "System.Private.Runtime.InteropServices.JavaScript")]
static Type jsRuntimeType = System.Type.GetType("System.Runtime.InteropServices.JavaScript.Runtime, System.Private.Runtime.InteropServices.JavaScript", true);
static Type jsFunctionType = System.Type.GetType("System.Runtime.InteropServices.JavaScript.Function, System.Private.Runtime.InteropServices.JavaScript", true);
[DynamicDependency("InvokeJS(System.String)", "System.Runtime.InteropServices.JavaScript.Runtime", "System.Private.Runtime.InteropServices.JavaScript")]
static MethodInfo invokeJSMethod = jsRuntimeType.GetMethod("InvokeJS", new Type[] { typeof(string) });
[DynamicDependency(DynamicallyAccessedMemberTypes.PublicConstructors, "System.Runtime.InteropServices.JavaScript.Function", "System.Private.Runtime.InteropServices.JavaScript")]
static ConstructorInfo functionConstructor = jsRuntimeType.GetConstructor(new Type[] { typeof(object[]) });
[DynamicDependency("Call()", "System.Runtime.InteropServices.JavaScript.Function", "System.Private.Runtime.InteropServices.JavaScript")]
static MethodInfo functionCall = jsFunctionType.GetMethod("Call", BindingFlags.Instance | BindingFlags.Public, new Type[] { });
public AppStartTask()
{
measurements = new Measurement[] {
new PageShow(),
new ReachManaged(),
};
}
Measurement[] measurements;
public override Measurement[] Measurements => measurements;
static string InvokeJS(string js)
{
return (string)invokeJSMethod.Invoke(null, new object[] { js });
}
class PageShow : BenchTask.Measurement
{
public override string Name => "Page show";
public override int InitialSamples => 3;
async Task RunAsyncStep()
{
var function = Activator.CreateInstance(jsFunctionType, new object[] { new object[] { @"return App.StartAppUI();" } });
var task = (Task<object>)functionCall.Invoke(function, new object[] { });
await task;
}
public override bool HasRunStepAsync => true;
public override async Task RunStepAsync()
{
var function = Activator.CreateInstance(jsFunctionType, new object[] { new object[] { @"return App.PageShow();" } });
await (Task<object>)functionCall.Invoke(function, null);
}
}
class ReachManaged : BenchTask.Measurement
{
public override string Name => "Reach managed";
public override int InitialSamples => 3;
public override bool HasRunStepAsync => true;
static object jsUIReachedManagedFunction = Activator.CreateInstance(jsFunctionType, new object[] { new object[] { @"return App.ReachedManaged();" } });
static object jsReached = Activator.CreateInstance(jsFunctionType, new object[] { new object[] { @"return App.reached();" } });
[MethodImpl(MethodImplOptions.NoInlining)]
public static void Reached()
{
functionCall.Invoke(jsReached, null);
}
public override async Task RunStepAsync()
{
await (Task<object>)functionCall.Invoke(jsUIReachedManagedFunction, null);
}
}
}
}
......@@ -6,7 +6,7 @@
using System.Text.RegularExpressions;
using System.Threading.Tasks;
abstract class BenchTask
public abstract class BenchTask
{
public abstract string Name { get; }
readonly List<Result> results = new();
......@@ -18,7 +18,7 @@ public async Task<string> RunBatch(List<Result> results, int measurementIdx, int
{
var measurement = Measurements[measurementIdx];
await measurement.BeforeBatch();
var result = measurement.RunBatch(this, milliseconds);
var result = await measurement.RunBatch(this, milliseconds);
results.Add(result);
await measurement.AfterBatch();
......@@ -50,14 +50,17 @@ public abstract class Measurement
public virtual Task AfterBatch() { return Task.CompletedTask; }
public abstract void RunStep();
public virtual void RunStep() { }
public virtual async Task RunStepAsync() { await Task.CompletedTask; }
public virtual bool HasRunStepAsync => false;
protected virtual int CalculateSteps(int milliseconds, TimeSpan initTs)
{
return (int)(milliseconds * InitialSamples / Math.Max(1.0, initTs.TotalMilliseconds));
}
public Result RunBatch(BenchTask task, int milliseconds)
public async Task<Result> RunBatch(BenchTask task, int milliseconds)
{
DateTime start = DateTime.Now;
DateTime end;
......@@ -65,12 +68,19 @@ public Result RunBatch(BenchTask task, int milliseconds)
try
{
// run one to eliminate possible startup overhead and do GC collection
RunStep();
if (HasRunStepAsync)
await RunStepAsync();
else
RunStep();
GC.Collect();
start = DateTime.Now;
for (i = 0; i < InitialSamples; i++)
RunStep();
if (HasRunStepAsync)
await RunStepAsync();
else
RunStep();
end = DateTime.Now;
var initTs = end - start;
......@@ -79,7 +89,10 @@ public Result RunBatch(BenchTask task, int milliseconds)
start = DateTime.Now;
for (i = 0; i < steps; i++)
{
RunStep();
if (HasRunStepAsync)
await RunStepAsync();
else
RunStep();
}
end = DateTime.Now;
......
......@@ -15,8 +15,9 @@ public partial class Test
{
List<BenchTask> tasks = new()
{
new AppStartTask(),
new ExceptionsTask(),
new JsonTask (),
new JsonTask(),
new WebSocketTask()
};
static Test instance = new Test();
......
......@@ -3,10 +3,14 @@
<!-- don't need to run this on helix -->
<WasmCopyAppZipToHelixTestDir>false</WasmCopyAppZipToHelixTestDir>
<WasmMainJSPath>runtime.js</WasmMainJSPath>
<SuppressTrimAnalysisWarnings>true</SuppressTrimAnalysisWarnings>
</PropertyGroup>
<ItemGroup>
<WasmExtraFilesToDeploy Include="index.html" />
<WasmExtraFilesToDeploy Include="appstart-frame.html" />
<WasmExtraFilesToDeploy Include="appstart.js" />
<WasmExtraFilesToDeploy Include="style.css" />
<Compile Remove="Console/Console.cs" />
</ItemGroup>
......
<!DOCTYPE html>
<!-- Licensed to the .NET Foundation under one or more agreements. -->
<!-- The .NET Foundation licenses this file to you under the MIT license. -->
<html>
<head>
<title>App task</title>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
</head>
<body>
<h3 id="header">Wasm Browser Sample - App task frame</h3>
<span id="out"></span>
<script type='text/javascript'>
var test_exit = function(exit_code)
{
/* Set result in a tests_done element, to be read by xharness */
var tests_done_elem = document.createElement("label");
tests_done_elem.id = "tests_done";
tests_done_elem.innerHTML = exit_code.toString();
document.body.appendChild(tests_done_elem);
console.log(`WASM EXIT ${exit_code}`);
};
window.addEventListener("pageshow", event => { window.parent.resolveAppStartEvent(event); })
var App = {
init: function () {
INTERNAL.call_static_method("[Wasm.Browser.Bench.Sample] Sample.AppStartTask/ReachManaged:Reached");
},
reached: function() {
window.parent.resolveAppStartEvent("reached");
}
};
</script>
<script type="text/javascript" src="runtime.js"></script>
<script defer src="dotnet.js"></script>
</body>
</html>
var AppStart = {
Construct: function() {
this._frame = document.createElement('iframe');
document.body.appendChild(this._frame);
},
WaitForPageShow: async function() {
let promise;
let promiseResolve;
this._frame.src = 'appstart-frame.html';
promise = new Promise(resolve => { promiseResolve = resolve; })
window.resolveAppStartEvent = function(event) { promiseResolve(); }
await promise;
},
WaitForReached: async function() {
let promise;
let promiseResolve;
this._frame.src = 'appstart-frame.html';
promise = new Promise(resolve => { promiseResolve = resolve; })
window.resolveAppStartEvent = function(event) {
if (event == "reached")
promiseResolve();
}
await promise;
},
RemoveFrame: function () {
document.body.removeChild(this._frame);
}
};
......@@ -6,6 +6,7 @@
<title>TESTS</title>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<link rel="stylesheet" href="style.css">
</head>
<body onload="onLoad()">
<h3 id="header">Wasm Browser Sample - Simple Benchmark</h3>
......@@ -53,10 +54,31 @@
if (tasks != '')
INTERNAL.call_static_method("[Wasm.Browser.Bench.Sample] Sample.Test:SetTasks", tasks);
yieldBench ();
},
PageShow: async function ()
{
AppStart.Construct();
try {
await AppStart.WaitForPageShow();
} finally {
AppStart.RemoveFrame();
}
},
ReachedManaged: async function ()
{
AppStart.Construct();
try {
await AppStart.WaitForReached();
} finally {
AppStart.RemoveFrame();
}
}
};
</script>
<script type="text/javascript" src="runtime.js"></script>
<script type="text/javascript" src="appstart.js"></script>
<script defer src="dotnet.js"></script>
......
......@@ -5,24 +5,16 @@
var Module = {
config: null,
configSrc: "./mono-config.json",
onConfigLoaded: function () {
if (MONO.config.enable_profiler) {
MONO.config.aot_profiler_options = {
write_at: "Sample.Test::StopProfile",
send_to: "System.Runtime.InteropServices.JavaScript.Runtime::DumpAotProfileData"
}
}
},
onDotNetReady: function () {
try {
App.init();
} catch (error) {
console.log("exception: " + error);
test_exit(1);
throw (error);
}
},
onAbort: function (err) {
test_exit(1);
},
};
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册