异步任务的执行能力非常差的线程池在.net运行原生 [英] Very poor performance of async task run on threadpool in .Net native

查看:312
本文介绍了异步任务的执行能力非常差的线程池在.net运行原生的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我观察到一个奇怪的差额管理VS净本土code。我已经重定向到线程池沉重的工作。当运行在托管code中的应用程序,一切工作顺利,但只要我打开本地编译 - 任务运行几次慢,缓慢,它挂起UI线程(我猜CPU是这么过载)。

I've observed a strange difference in managed vs .Net native code. I've a heavy job redirected to threadpool. When running the app in managed code, everything works smooth but as soon as I switch on native compilation - the task run few times slower and so slow that it hangs UI thread (I guess CPU is so overloaded).

下面是从调试输出两个截图,在左侧的一个是从管理code和右边的一个是从本地编译。正如你可以看到UI任务所消耗的时间是在两种情况下几乎相同,最多的时候线程池作业开始时间 - 然后在托管版本的UI经过时间的增长(事实上UI被封锁,不能采取任何行动)。线程池工作时序为自己说话。

Here are two screenshots from debug output, the one on the left is from managed code, and the one on the right is from native compilation. As you can see the time consumed by UI task is nearly the same in both cases, up to a time when threadpool job is started - then in managed version UI elapsed time grows (in fact UI gets blocked and you cannot take any action). Timings of threadpool job speak for themselves.

href=\"http://i.stack.imgur.com/qof4pm.png\">

样品code重现该问题:

The sample code to reproduce the problem:

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

如果你需要一个完整的示例 - 那么你可以download在这里的。

If you need a complete sample - then you can download it here.

在我测试过的差别出现在两个优化/非优化code,在这两种调试和发布版本。

As I've tested the difference appears on both optimized/non optimized code, in both debug and release versions.

没有任何人有一个想法是什么可能会导致问题?

Does anybody have an idea what can cause the problem?

推荐答案

此问题是因为线程池数学循环导致GC饥饿引起的。从本质上讲,GC已决定它需要运行(由于希望做一些互操作分配),它的试图阻止所有线程做收集/压缩。不幸的是,我们还没有添加对.NET本机劫持热循环就像你有如下的能力。这是一个关于 迁移你的Windows应用商店简要地提及应用到.NET本地 页为:

This issue is caused because the "ThreadPool" math loop is causing GC starvation. Essentially, the GC has decided that it needs to run (due to wanting to do some interop allocation) and it’s trying to stop all of the threads to do collection/compaction. Unfortunately, we haven’t added the ability for .NET Native to hijack hot loops like the one you have below. This is briefly mentioned on Migrating Your Windows Store App to .NET Native page as:

无限循环不打电话,(例如,而(真);)在任何线程可能带来的应用停顿。同样的,大的或无限的等待可能带来的应用停顿。

Infinite looping without making a call (for example, while(true);) on any thread may bring the app to a halt. Similarly, large or infinite waits may bring the app to a halt.

一个办法来解决,这是添加一个调用点到你的循环(GC很高兴中断时,它试图调用另一个方法!你的线程)。

One way to work around this is to add a call site into your loop (the GC is very happy to interrupt your thread when it’s trying to call another method!).

    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()
    {
    }

缺点是,你有这样的丑寻找黑客,你可以从附加说明受些影响。我已经让这里的一些人知道这件事,我们认为是微乎其微罕见实际上是由客户打,我们会看到什么办法来完成。

The downside is that you’ll have this "ugly" looking hack and you may suffer a bit from the added instructions. I've let some folks here know that this thing that we assumed was "vanishingly rare" is actually hit by a customer and we'll see what can be done about it.

谢谢你的报告!

更新:我们已经围绕此方案的一些重大改进,将能够劫持最长时间运行的线程GC。这些修补程序将在四月更新2集UWP工具,在可能是可用? (我不控制船期:-))

Update: We have made some big improvements around this scenario and will be able to hijack most long running threads for GC. These fixes will be available in the Update 2 set of UWP tools out probably in April? (I don't control the shipping schedule :-) )

更新更新:新的工具,现已作为UWP工具1.3.1部分。我们不希望有一个完美的解决方案,以积极的线程对抗由GC被劫持,但我希望这种情况能与最新的工具好得多。让我们知道!

Update update: The new tools are now available as part of UWP tools 1.3.1. We don't expect to have a perfect solution to threads aggressively fighting against being hijacked by the GC but I expect this scenario to be much better with the latest tools. Let us know!

这篇关于异步任务的执行能力非常差的线程池在.net运行原生的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆