Blazor wasm вызывает javascript, передача большого массива происходит очень медленно

#javascript #performance #blazor #blazor-webassembly

#javascript #Производительность #blazor #blazor-webassembly

Вопрос:

У меня есть приложение blazor wasm. В этом я вызываю функцию javascript, которая получает массив double. Это происходит очень медленно, особенно когда массив большой.

Для проверки смотрите код ниже:

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;
}
  

И код c # (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");
    }
}
  

И для завершения — 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>
  

Запуск этого один раз дает следующий результат на моей машине:

Из JS время суммирования: 0.0037800000282004476 с

Время C # для вызова js и суммирования: 7.000 с

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);
}
  

и с помощью JSON string и 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);
}
  

Все методы занимают примерно одинаковое время, 6-7 секунд для завершения (из них около 0,0015-0,006 секунды в функции javascript).

Я попытался выяснить, как вызвать немаршированную передачу массива, используя BINDING.mono_array_to_js_array найденный в в этом файле, но это выдает длинную ошибку. 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;
}
  

Комментарии:

1. Какой браузер вы используете? Будет ли это медленно или быстро с webassembly, зависит от реализации webassembly браузера. Поэтому они лучше других…

2. @Vencovsky только что быстро протестировал chrome..

3. Не уверен, что означает «quickltmy»

4. быстро было то, что я имел в виду 🙂 — автозапуск..

5. Chrome 7 секунд, Fireforx 6 секунд, Edge (v. 44) выходит из строя..

Ответ №1:

Просто нашел способ использовать.массивы чистых байтов или с плавающей точкой в 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);
}
  

Здесь результатом являются объекты Uint8Array и Float32Array из byte[] и float [] соответственно в течение разумного периода времени.

Может быть, есть какие-либо подходы к получению массивов js, потому что у вас есть доступ ко всему.чистая куча из ArrayBuffers, подобных модулю.HEAPU8 (куча внутри Uint8Array) или модуль.HEAPF32 (куча внутри Float32Array) и может легко обращаться к объектам по указателю из параметров InvokeUnmarshalled.

Комментарии:

1. Большое спасибо, это действительно ускоряет процесс!

2. Спасибо, это работает как шарм, и увеличение производительности заметно.

3. Привет @lg101, большое вам спасибо за ваше исследование! Я изучаю возможность чтения символа[] из кучи. Не могли бы вы сказать мне, как нам получить «r»? и почему мы добавляем 4 к «m»?