Десериализация Blazor WebAssembly намного медленнее, чем даже загрузка? Что случилось?

В моем приложении Blazor у меня есть компонент с таким методом. (Я заменил вызов GetFromJsonAsync кодом изнутри, чтобы сузить медленную часть.)

  private async Task GetData()
  {
      IsLoading = true;
      string url = $".../api/v1/Foo";  // will return a 1.5 MB JSON array
      var client = clientFactory.CreateClient("MyNamedClient");

      Console.WriteLine($"starting");

      List<Foo> results;

      Task<HttpResponseMessage> taskResponse = client.GetAsync(url, HttpCompletionOption.ResponseContentRead, default);

      var sw = Stopwatch.StartNew();
      using (HttpResponseMessage response = await taskResponse)
      {
        
        response.EnsureSuccessStatusCode();
        var content = response.Content!;

        if (content == null)
        {
          throw new ArgumentNullException(nameof(content));
        }
        
        string contentString = await content.ReadAsStringAsync();

        sw.Stop();
        Console.WriteLine($"Read string: {sw.Elapsed}");
        sw.Restart();

        results = System.Text.Json.JsonSerializer.Deserialize<List<Foo>>(contentString);

      }

      sw.Stop();
      Console.WriteLine($"Deserialize: {sw.Elapsed}");
      
      StateHasChanged();
      IsLoading = false;

Моя загрузка 1,5 МБ занимает 1-6 секунд, но остальная часть операции (во время которой пользовательский интерфейс заблокирован) занимает 10-30 секунд. Это просто медленная десериализация в ReadFromJsonAsync (которая вызывает System.Text.Json.JsonSerializer.Deserialize внутренне), или здесь что-то еще происходит? Как повысить эффективность получения такого большого набора данных (хотя, я думаю, он не такой уж и большой!)

Я закомментировал все, что связано с Results, чтобы упростить, и вместо этого у меня просто есть индикатор, привязанный к IsLoading. Это говорит мне, что нет проблем с обновлением DOM или рендерингом.

Когда я пробую тот же набор кода в автоматическом интеграционном тесте, это занимает всего 3 секунды или около того. Действительно ли WebAssembly так медленно выполняет десериализацию? Если да, то является ли единственное решение извлекать очень маленькие наборы данных повсюду на моем сайте? Мне это не кажется правильным.

Вот итоговый журнал консоли браузера при выполнении вышеуказанного кода:

VM1131:1 Fetch finished loading: GET "https://localhost:5001/api/v1/Foo".
read string 00:00:05.5464300
Deserialize: 00:00:15.4109950
L: GC_MAJOR_SWEEP: major size: 3232K in use: 28547K
L: GC_MAJOR: (LOS overflow) time 18.49ms, stw 18.50ms los size: 2048K in use: 187K
L: GC_MINOR: (LOS overflow) time 0.33ms, stw 0.37ms promoted 0K major size: 3232K in use: 2014K los size: 2048K in use: 187K

Вот график производительности Chrome. Зеленый цвет - загрузка, а оранжевый - выполнение микрозадач, что, как я полагаю, означает работу WebAssembly.

введите описание изображения здесь


person Patrick Szalapski    schedule 04.08.2020    source источник
comment
Посмотрим, лучше ли Newtonsoft   -  person Peter Morris    schedule 05.08.2020
comment
Я отредактировал пример, чтобы он был более ясным, а также чтобы фактически отделить загрузку от десериализации.   -  person Patrick Szalapski    schedule 05.08.2020
comment
Ответ, вероятно, да, это медленно, это работает как интерпретируемый код IL. Вероятно, будут небольшие улучшения при переходе на .net 5, но только когда мы получим компиляцию AoT, она приблизится к «нормальным» скоростям. Не уверен, каков график.   -  person Henk Holterman    schedule 05.08.2020
comment
Любые предложения о том, что делает это медленным, или как обойтись? Это единственный способ получить более мелкие фрагменты данных? Есть ли способ упростить сериализацию или как-то работать с несериализованными / менее структурированными данными?   -  person Patrick Szalapski    schedule 05.08.2020
comment
Добавил его в проблемы github.com/dotnet/aspnetcore/issues/24581, но я все еще задаюсь вопросом, есть ли что-то, что я делаю не так.   -  person Patrick Szalapski    schedule 05.08.2020
comment
@PeterMorris Использование Newtonsoft.Json.JsonConvert.DeserializeObject вместо System.Text.Json.JsonSerializer.Deserialize дает очень похожие результаты.   -  person Patrick Szalapski    schedule 05.08.2020
comment
1,5 МБ - это много JSON для загрузки и десериализации. Вам абсолютно необходимо загрузить столько данных? Его можно вывести на страницы или что-то в этом роде? Из любопытства, сколько времени занимает JavaScript, чтобы десериализовать его?   -  person Peter Morris    schedule 05.08.2020


Ответы (1)


или здесь что-то еще происходит?

Так выглядит. Вы не указали, какое оборудование вы используете, но на моем рабочем столе (i7) я могу загрузить его за ‹100 мс (локальный хост), а десериализация займет‹ 4 секунды. Нет большой разницы между режимами отладки и выпуска или между браузерами.

Итак, 5 секунд для загрузки с локального хоста - это уже очень странно. Что-то не так в вашей настройке.

MCVE:

  • Начните с шаблона Wasm Hosted
  • Сделайте так, чтобы контроллер возвращал ~ 17000 элементов (было 5), чтобы получить ~ 1,5 МБ
  • Поместите свой код в FetchData OnInit (), замените Foo на WeatherForecast
  • Не пытайтесь воспроизвести их все.

Посмотрите, какие результаты вы получите от этого.

person Henk Holterman    schedule 06.08.2020
comment
У меня действительно есть 7-12 секунд, чтобы вернуть 17000 элементов (около 1,6 МБ) WeatherForecast. (Время загрузки на localhost составляет около 20 мс.) С использованием значения по умолчанию await Http.GetFromJsonAsync<WeatherForecast[]>("WeatherForecast"); Таким образом, это похоже на время в моем немного более сложном случае в исходном вопросе. (К вашему сведению, я также обновил System.Text.Json через NuGet до версии 5.0.0-preview.7, но это не сильно помогло.) - person Patrick Szalapski; 06.08.2020
comment
(Кроме того, я увеличил полезную нагрузку до 5 МБ, и это заняло 23-27 секунд.) - person Patrick Szalapski; 06.08.2020