2016-01-07 76 views
21

我在管理的vs .net本機代碼中觀察到了一個奇怪的差異。我有一個重要的工作重定向到線程池。在託管代碼中運行應用程序時,一切運行平穩,但只要我打開本機編譯 - 任務運行速度會慢幾倍,速度太慢,導致UI線程掛起(我猜CPU太重了)。在.Net本身的線程池上運行異步任務的性能很差

這裏有兩個來自調試輸出的截圖,左邊的一個來自託管代碼,右邊的來自本地編譯。正如你所看到的,在這兩種情況下,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 Native Compiler和Runtime團隊工作。我們通常使用PerfView進行這些調查。如果你可以收集一些etl的痕跡(一個是.net native和一個沒有的),然後以我們的方式發送給我們([email protected]),我們會讓人們看看它。 –

+1

可能是一個線程池飢餓。你玩過「ThreadPool.SetMinThreads/SetMaxThreads」嗎? – Noseratio

回答

14

此問題是由於「ThreadPool」數學循環導致GC不足造成的。從本質上講,GC已經決定需要運行(因爲想做一些互操作分配),它試圖阻止所有的線程進行收集/壓縮。不幸的是,我們沒有添加.NET Native的功能來劫持像下面那樣的熱循環。這是在Migrating Your Windows Store App to .NET Native頁面,簡單地提到:

無限循環不打電話,(例如,而(真);)在任何線程可能帶來的應用停頓。同樣,大量或無限的等待可能會導致應用程序停止。

解決此問題的一種方法是將一個調用站點添加到您的循環中(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工具的更新2中提供。 (我不控制發貨計劃:-))

更新更新:新工具現在作爲UWP工具1.3.1的一部分提供。我們並不期望有一個完美的解決方案來積極對抗被GC劫持的線程,但我希望這個場景能夠用最新的工具更好。讓我們知道!

+2

感謝整個團隊的照顧。你的一位同事表示,這將在下一次VS更新中得到糾正 - 這很好。我同意,我很難在桌面上重現這個問題,但在ARM上,我認爲這是一個真實的場景 - 事實上,我在我的應用程序中觀察到了這一點。我有一個處理照片的方法,並且對像素執行一些數學運算,因爲它耗費CPU資源,將它重定向到線程池,這是我發現問題的地方。再一次感謝你。 – Romasz

+1

我也編輯了很少的答案,並將MSDN鏈接設置爲大膽的 - 這有可能幫助某人節省一些時間。 – Romasz

+1

編輯非常棒!我的SO標記不是很好,所以我非常感謝!聽起來好像我們將在Update 2中爲這類事情做一些修復。 –