繁体   English   中英

在.Net native中的线程池上运行异步任务的性能很差

[英]Very poor performance of async task run on threadpool in .Net native

我发现托管vs .Net本机代码有一个奇怪的区别。 我有一个繁重的工作重定向到线程池。 当在托管代码中运行应用程序时,一切都运行顺畅,但是一旦我打开本机编译 - 任务运行速度慢几十倍,以至于它挂起UI线程(我猜CPU是如此过载)。

以下是调试输出的两个屏幕截图,左侧的屏幕截图来自托管代码,右侧的屏幕截图来自本机编译。 正如您所看到的,UI任务所消耗的时间在两种情况下几乎相同,直到启动线程池作业时 - 然后在托管版本中UI经过的时间增长(实际上UI被阻止而您无法采取任何操作)。 线程池工作的时间不言自明。

管理 本地人

重现问题的示例代码:

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

如果您需要完整的样本 - 那么您可以在此处下载

正如我所测试的那样,在调试和发布版本中,优化/非优化代码都会出现差异。

有谁知道会导致什么问题?

导致此问题的原因是“ThreadPool”数学循环导致GC饥饿。 从本质上讲,GC已经决定它需要运行(由于想要进行一些互操作分配)并且它试图阻止所有线程进行收集/压缩。 不幸的是,我们还没有添加.NET Native能够劫持热循环的能力,就像你下面的那样。 有关将.NET应用商店应用迁移到.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线程。 这些修复程序可能会在4月份的Update 2 UWP工具集中提供吗? (我不控制出货时间表:-))

更新更新:新工具现已作为UWP工具1.3.1的一部分提供。 我们不希望有一个完美的解决方案来积极地反对被GC劫持的线程,但我希望使用最新工具可以更好地利用这种情况。 让我们知道!

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM