2016-01-07 43 views
21

Ho notato una strana differenza nel codice nativo gestito vs .Net. Ho un lavoro pesante reindirizzato a threadpool. Quando si esegue l'app nel codice gestito, tutto funziona senza problemi, ma non appena accendo la compilazione nativa - l'attività viene eseguita alcune volte più lentamente e così lentamente da bloccare il thread dell'interfaccia utente (suppongo che la CPU sia così sovraccaricata).Prestazioni molto scarse dell'attività asincrona eseguita su threadpool in. Netto nativo

Ecco due schermate dall'output di debug, quella a sinistra proviene dal codice gestito e quella a destra proviene dalla compilazione nativa. Come puoi vedere, il tempo impiegato dall'interfaccia utente è quasi lo stesso in entrambi i casi, fino al momento in cui viene avviato il processo threadpool, quindi nell'UI della versione gestita il tempo trascorso aumenta (infatti l'interfaccia utente viene bloccata e non è possibile eseguire alcuna azione). I tempi del lavoro Threadpool parlano da soli.

ManagedNative

Il codice di esempio per riprodurre il problema:

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

Se avete bisogno di un campione completo - allora si può download it here.

Come ho provato, la differenza appare sia sul codice ottimizzato/non ottimizzato, sia nella versione di debug e di rilascio.

Qualcuno ha un'idea di cosa può causare il problema?

+2

Potrebbe essere necessario dare un'occhiata all'IL e al codice macchina emessi. – Rob

+4

Lavoro con il team .NET Native Compiler and Runtime.Di solito utilizziamo PerfView per questo tipo di indagini. Se riesci a raccogliere alcune tracce di etl (una con .net nativa e una senza) e le abbiamo inviate a nostra maniera ([email protected]), vedremo qualcuno che le darà un'occhiata. –

+1

Potrebbe essere un inaridimento del pool di thread. Hai giocato con 'ThreadPool.SetMinThreads/SetMaxThreads'? – Noseratio

risposta

14

Questo problema è causato dal fatto che il ciclo matematico "ThreadPool" sta causando la fame a GC. In sostanza, il GC ha deciso che deve essere eseguito (a causa della volontà di eseguire alcune allocazioni di interoperabilità) e sta tentando di interrompere tutti i thread per eseguire la raccolta/compattazione. Sfortunatamente, non abbiamo aggiunto la possibilità per .NET Native di dirottare i loop caldi come quello qui sotto. Questo è brevemente accennato sopra Migrating Your Windows Store App to .NET Native pagina come:

loop infinito senza effettuare una chiamata (ad esempio, mentre (true);) in alcun filo potrebbe portare all'applicazione di un arresto. Allo stesso modo, le attese grandi o infinite possono arrestare l'applicazione.

Un modo per ovviare a questo è aggiungere un sito di chiamata nel ciclo (il GC è molto felice di interrompere il thread quando tenta di chiamare un altro metodo!).

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

Lo svantaggio è che avrete questo “brutto” in cerca trucco e si può soffrire un po 'dalle istruzioni aggiunti. Ho lasciato che alcune persone qui sapessero che questa cosa che pensavamo fosse "incredibilmente rara" è in realtà colpita da un cliente e vedremo cosa si può fare al riguardo.

Grazie per il rapporto!

Aggiornamento: abbiamo apportato alcuni grandi miglioramenti in questo scenario e saremo in grado di dirottare la maggior parte dei thread di lunga durata per GC. Queste correzioni saranno disponibili nel set di aggiornamenti 2 degli strumenti UWP probabilmente in aprile? (Non controllo il programma di spedizione :-))

Aggiornamento aggiornamento: i nuovi strumenti sono ora disponibili come parte degli strumenti UWP 1.3.1. Non ci aspettiamo di avere una soluzione perfetta per le discussioni che combattono in modo aggressivo contro il dirottamento da parte del GC, ma mi aspetto che questo scenario sia molto migliore con gli strumenti più recenti. Facci sapere!

+2

Grazie per l'intera squadra per aver cura di questo. Uno dei tuoi colleghi ha detto che questo sarà corretto nel prossimo aggiornamento VS: è grandioso. Concordo sul fatto che è stato più difficile per me riprodurre il problema sul desktop, ma su ARM penso che sia uno scenario reale - in effetti l'ho osservato nella mia app. Ho un metodo che si occupa di foto e fa alcune operazioni matematiche su pixel, dato che consuma CPU, viene reindirizzato a threadpool, questo è il punto in cui ho individuato il problema. Grazie ancora. – Romasz

+1

Ho anche modificato poco la tua risposta e ho reso il link MSDN un grassetto - c'è una possibilità che questo possa aiutare qualcuno a risparmiare un po 'di tempo. – Romasz

+1

Le modifiche sono fantastiche! Il mio markup SO non è eccezionale quindi lo apprezzo davvero! Sembra che avremo alcune correzioni per questo genere di cose nell'aggiornamento 2. –