Подтвердить что ты не робот

Очень низкая производительность async-задачи выполняется на threadpool в .Net native

Я наблюдал странную разницу в управляемом vs .Net-коде. У меня тяжелая работа перенаправлена ​​на threadpool. При запуске приложения в управляемом коде все работает плавно, но как только я включаю встроенную компиляцию - задача выполняется несколько раз медленнее и так медленно, что она зависает от потока пользовательского интерфейса (я думаю, что процессор настолько перегружен).

Вот два скриншота из отладочного вывода, слева - управляемый код, а один справа - из встроенной компиляции. Поскольку вы можете видеть, что время, затрачиваемое на выполнение задачи пользовательского интерфейса, почти одинаковое в обоих случаях, до момента запуска задания threadpool - тогда в управляемой версии увеличивается истекшее время UI (фактически UI блокируется, и вы не можете предпринимать никаких действий). Сроки работы в threadpool говорят сами за себя.

Управляемый Родной

Пример кода для воспроизведения проблемы:

private int max = 2000;
private async void UIJob_Click(object sender, RoutedEventArgs e)
{
    IProgress<int> progress = new Progress<int>((p) => { MyProgressBar.Value = (double)p / max; });
    await Task.Run(async () => { await SomeUIJob(progress); });
}

private async Task SomeUIJob(IProgress<int> progress)
{
    Stopwatch watch = new Stopwatch();
    watch.Start();
    for (int i = 0; i < max; i++)
    {
        if (i % 100 == 0) { Debug.WriteLine($"     UI time elapsed => {watch.ElapsedMilliseconds}"); watch.Restart(); }
        await Task.Delay(1);
        progress.Report(i);
    }
}

private async void ThreadpoolJob_Click(object sender, RoutedEventArgs e)
{
    Debug.WriteLine("Firing on Threadpool");
    await Task.Run(() =>
   {
       double a = 0.314;
       Stopwatch watch = new Stopwatch();
       watch.Start();
       for (int i = 0; i < 50000000; i++)
       {
           a = Math.Sqrt(a) + Math.Sqrt(a + 1) + i;
           if (i % 10000000 == 0) { Debug.WriteLine($"Threadpool -> a value = {a} got in {watch.ElapsedMilliseconds} ms"); watch.Restart(); };
       }
   });
    Debug.WriteLine("Finished with Threadpool");
}

Если вам нужен полный образец, то вы можете загрузить его здесь.

Как я уже протестировал, разница как в оптимизированном/не оптимизированном коде, так и в версии отладки и выпуска.

Есть ли у кого-нибудь идеи, что может вызвать проблему?

4b9b3361

Ответ 1

Эта проблема вызвана тем, что математический цикл ThreadPool вызывает голодание GC. По сути, GC решил, что он должен запускаться (из-за желания сделать некоторое переключение между группами) и пытается остановить все потоки для сбора/сжатия. К сожалению, мы не добавили возможности для .NET-Native захватить горячие циклы, как у вас ниже. Это кратко упоминается в Перенос вашего приложения для Windows Store на .NET Native:

Бесконечный цикл без вызова (например, while (true);) в любом потоке может остановить приложение. Точно так же большие или бесконечные ожидания могут привести к остановке приложения.

Один из способов обойти это - добавить сайт вызова в ваш цикл (GC очень рад прервать ваш поток, когда он пытается вызвать другой метод!).

    for (long i = 0; i < 5000000000; i++)
           {
               MaybeGCMeHere(); // new callsite
               a = Math.Sqrt(a) + Math.Sqrt(a + 1) + i;
               if (i % 1000000000 == 0) { Debug.WriteLine($"Threadpool -> a value = {a} got in {watch.ElapsedMilliseconds} ms"); watch.Restart(); };
    }

...

    [MethodImpl(MethodImplOptions.NoInlining)] // need this so the callsite isn’t optimized away
    private void MaybeGCMeHere()
    {
    }

Недостатком является то, что у вас будет этот "уродливый" вид взлома, и вы можете немного пострадать от добавленных инструкций. Я позволил некоторым людям узнать, что эта вещь, которую мы предположили, была "исчезающе редка", на самом деле поражена клиентом, и мы увидим, что с этим можно сделать.

Спасибо за отчет!

Обновление. Мы сделали некоторые улучшения в этом сценарии и можем захватить самые длинные потоки для GC. Эти исправления будут доступны в наборе инструментов UWP Update 2, вероятно, в апреле? (Я не контролирую график доставки:-))

Обновление обновления. Новые инструменты теперь доступны как часть инструментов 1.3.1 UWP. Мы не ожидаем, что у нас будет идеальное решение для потоков, активно сражающихся против захвата GC, но я ожидаю, что этот сценарий будет намного лучше с последними инструментами. Дайте нам знать!