Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Blazor wasm invoke javascript, pass large array is very slow

I have a blazor wasm app. In that I am invoking a javascript function that receives an array of double. This is very slow, especially when the array is large.

For a test see the code below:

javascript ("test.js"):

function testSumArray(array) {
    var t0 = performance.now();
    sumArray(array);
    var t1 = performance.now();
    console.log('From JS, time to sum: ' + (t1 - t0) / 1000 + ' s');
}

function sumArray(array) {
    var i;
    var s = 0;
    for (i = 0; i < array.length; i++) {
        s += array[i];
    }
    return s;
}

And c# code (index.razor):

@page "/"
@inject IJSRuntime JSRuntime;

@using System.Text
@using BlazorWasmOnlyTest.Shared
<h1>Hello, world!</h1>

Welcome to your new app.

<div class="container">
    <div class="row mb-2">
        <div class="col">
            <button class="btn btn-primary" @onclick="@TestInvokeJS">Test invoke js</button>
        </div>
    </div>
</div>

@code {
    private int _id;
    private string _status = "";
    private DataInputFileForm _dataInputFileForm;

    private async void TestInvokeJS()
    {
        var n = 100000;
        var array = new double[n];
        for (int i = 0; i < n; i++)
        {
            array[i] = i;
        }
        var w = new System.Diagnostics.Stopwatch();
        w.Start();
        await JSRuntime.InvokeVoidAsync("testSumArray",array);
        w.Stop();
        Console.WriteLine($"C# time to invoke js and sum: {w.ElapsedMilliseconds/1000:F3} s");
    }
}

And for completion - index.html:

<!DOCTYPE html>
<html>

<head>
    <meta charset="utf-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no" />
    <title>BlazorWasmOnlyTest</title>
    <base href="/" />
    <link href="css/bootstrap/bootstrap.min.css" rel="stylesheet" />
    <link href="css/app.css" rel="stylesheet" />
    <script src="js/test.js"></script>
</head>

<body>
    <app>Loading...</app>

    <div id="blazor-error-ui">
        An unhandled error has occurred.
        <a href="" class="reload">Reload</a>
        <a class="dismiss">🗙</a>
    </div>
    <script src="_framework/blazor.webassembly.js"></script>
</body>

</html>

Running this once gives the following output on my machine:

From JS, time to sum: 0.0037800000282004476 s

C# time to invoke js and sum: 7.000 s

That seems like a pretty high overhead time... Does anyone know if there is a way to speed this up (the real function does something I presently cannot do in Blazor/C# - updating a layer in Leaflet)

EDIT: I have tried the synchronous method described here, without any difference in execution time.

c#:

    var jsInProcess2 = (IJSInProcessRuntime)JSRuntime;
    jsInProcess2.InvokeVoid("testSumArray", array);

js: javascript same as testSumArray above.

EDIT 2:

I have tried passing a JSON string with synchronous interop:

c#:

    var jsInProcess3 = (IJSInProcessRuntime)JSRuntime;
    var array_json3 = System.Text.Json.JsonSerializer.Serialize(array);
    jsInProcess3.InvokeVoid("testSumArray3", array_json);

js:

function testSumArray3(array_json_string) {
    var t0 = performance.now();
    var array = JSON.parse(array_json_string);
    var s = sumArray(array);
    var t1 = performance.now();
    console.log('From JS, time to sum: ' + (t1 - t0) / 1000 + ' s');
    console.log('Array sum = ' + s);
}

and with JSON string and InvokeUnmarshalled js interopcall:

c#:

    var jsInProcess4 = (Microsoft.JSInterop.WebAssembly.WebAssemblyJSRuntime)JSRuntime;
    var array_json4 = System.Text.Json.JsonSerializer.Serialize(array);
    jsInProcess4.InvokeUnmarshalled<string,string>("testSumArray4", array_json4);

js:

function testSumArray4(array_mono_json_string) {
    var t0 = performance.now();
    const array_json_string = BINDING.conv_string(array_mono_json_string);
    var array = JSON.parse(array_json_string);
    var s = sumArray(array);
    var t1 = performance.now();
    console.log('From JS, time to sum: ' + (t1 - t0) / 1000 + ' s');
    console.log('Array sum = ' + s);
}

All methods take approximately the same time, 6-7 secs to complete (of that about 0.0015-0.006 seconds in the javascript function).

I have tried to figure out how to call unmarshalled passing an array, using BINDING.mono_array_to_js_array found in in this file but that throws a long error. c#:

    var sum = jsInProcess4.InvokeUnmarshalled<double[],double>("testSumArray5",array)

js:

function testSumArray5(array_in) {
    var t0 = performance.now();
    var array = BINDING.mono_array_to_js_array(array_in);
    console.log(array[0]);
    var s = sumArray(array);
    var t1 = performance.now();
    console.log('From JS, time to sum: ' + (t1 - t0) / 1000 + ' s');
    console.log('Array sum = ' + s);
    return s;
}
like image 607
Erik Thysell Avatar asked Sep 15 '20 13:09

Erik Thysell


People also ask

Is Blazor Wasm faster than JavaScript?

In essence, you can write C# code where you'd previously use JavaScript, and Blazor will compile that C# to WebAssembly to allow it to run in the browser. However, I was a little surprised to find that when I tested the speed of it versus the application written in JavaScript, it was noticeably slower.

Is Blazor slower than JavaScript?

If your app needs to access DOM frequently, then definitely web assembly / Blazor will be slower compared to any JS libraries since web assembly can’t directly access DOM without using Invokes (Which is slow at the moment, please refer my blazer benchmark below).

Is Blazor WebAssembly slow?

Performance. Performance in Blazor WASM has always been slow when compared to a JavaScript app — we find start-up the biggest issue. Once the app has loaded it's generally super fast for general use (e.g. navigating between pages). Right now you won't get great scores in Lighthouse, but we cross fingers for the future.

Can I use JavaScript in Blazor?

A Blazor app can invoke JavaScript (JS) functions from . NET methods and . NET methods from JS functions. These scenarios are called JavaScript interoperability (JS interop).


1 Answers

Just found the way to use .net byte or float arrays in js.

c#:

[Inject] //Injected JSRuntime from Blazor DI
private IJSRuntime JSRuntime { get; set; }

byte[] bytes1;
float[] floats2;
...
if (JSRuntime is IJSUnmarshalledRuntime webAssemblyJSRuntime)
{
    webAssemblyJSRuntime.InvokeUnmarshalled<byte[], float[], object> 
        ("downloadArray", bytes1, floats2);
}

JavaScript:

function downloadArray(bytes1, floats2) {
    // Easy way to convert Uint8 arrays
    var byteArray = Blazor.platform.toUint8Array(bytes1);

    // Adapted method above for float32
    var m = floats2 + 12;
    var r = Module.HEAP32[m >> 2]
    var j = new Float32Array(Module.HEAPF32.buffer, m + 4, r);
}

Here result is Uint8Array and Float32Array objects from byte[] and float[] respectively within a reasonable period of time.

May be there are any approaches to get js arrays because you have access to the whole .net heap from ArrayBuffers like Module.HEAPU8 (heap inside Uint8Array) or Module.HEAPF32 (heap inside Float32Array) and can easily access objects by pointer from InvokeUnmarshalled parameters.

like image 56
Ig101 Avatar answered Oct 02 '22 12:10

Ig101