2016-01-07 8 views
21

管理対象vs .Netネイティブコードに奇妙な違いがあります。私はスレッドプールにリダイレクトされた重い仕事をしています。アプリケーションをマネージコードで実行すると、すべてがスムーズに機能しますが、ネイティブコンパイルをオンにするとすぐに、タスクはUIスレッドをハングアップするほど遅く、遅くなります(CPUが非常にオーバーロードされていると思います)。.Netネイティブのスレッドプールで実行される非同期タスクのパフォーマンスが非常に悪い

ここにはデバッグ出力の2つのスクリーンショットがあり、左側のコードはマネージコードのもので、右側のコードはネイティブコンパイルのものです。表示されているように、UIタスクで費やされる時間は、どちらの場合も、スレッドプールジョブが開始されるまでほぼ同じです。その後、マネージドバージョンUIの経過時間が長くなります(UIはブロックされ、何もできません)。スレッドプールの仕事のタイミングは自分自身で話す。

ManagedNative

サンプルコード問題再現する:あなたは完全なサンプルが必要な場合は

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 it here

私は違いをテストしてきたように、両方のデバッグでは、両方の最適化/非最適化されたコードに表示されたバージョンをリリース。

問題の原因は何ですか?

+2

放射されたILとマシンコードを調べる必要があります。 – Rob

+4

私は.NETネイティブコンパイラとランタイムチームを担当しています。私たちは通常、この種の調査にPerfViewを使用します。いくつかのetlトレース(1つは.netのネイティブと1つありません)を収集し、私たちの方法([email protected])を送信すると、誰かに見てもらえるようになります。 –

+1

スレッドプールの飢餓である可能性があります。 'ThreadPool.SetMinThreads/SetMaxThreads'で遊んだことはありますか? – Noseratio

答えて

14

「ThreadPoolの」数学のループは、GCの飢餓を引き起こしているため、この問題は発生します。本質的に、GCは実行する必要があると判断し(interop割り当てをいくつか行いたいため)、すべてのスレッドが収集/圧縮を停止しようとしています。残念ながら、.NETネイティブが以下のようなホットループをハイジャックする機能は追加していません。停止にアプリをもたらす可能性のあるスレッドに、(例えば、)真(しばらく)の呼び出しを行うことなく

無限ループ:これは簡潔にMigrating Your Windows Store App to .NET Nativeページに記載されています。同様に、大規模なまたは無限の待機は、アプリケーションを停止する可能性があります。

これを回避する方法の1つは、コールサイトをループに追加することです(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月にUWPツールのUpdate 2セットで利用可能になるでしょうか? (私は出荷スケジュールを管理していません:-))

アップデートアップデート:新しいツールは、UWPツール1.3.1の一部として利用できるようになりました。 GCによってハイジャックされることに積極的に対処する完全なソリューションは期待していませんが、このシナリオは最新のツールではるかに優れていると思います。我々に教えてください!

+2

これを世話してくれたチーム全体に感謝します。あなたの同僚の一人は、次のVSアップデートでこれが修正されると述べています。それは素晴らしいことです。デスクトップ上で問題を再現するのは難しいと私は同意しますが、ARMでは実際のシナリオだと思っています。実際、私のアプリでこれを観察しました。私は、写真を扱い、ピクセル上で数学演算を行うメソッドを持っています.CPUが消費され、スレッドプールにリダイレクトされます。これが問題の原因です。もう一度ありがとうございます。 – Romasz

+1

私はあなたの答えもほとんど編集しておらず、MSDNのリンクを大胆にしました。これは、時間を節約するために役立つ可能性があります。 – Romasz

+1

編集が素晴らしいです!私のSOマークアップは素晴らしくないので、本当に感謝しています! Update 2では、このような問題に対していくつかの修正があるように思えます。 –

関連する問題