2015-11-18 67 views
6

Ho il seguente codice C# cercando di punto di riferimento in modalità di rilascio:Esecuzione lenta sotto 64 bit. Possibile errore RyuJIT?

using System; 
using System.Collections.Generic; 
using System.Diagnostics; 
using System.Linq; 
using System.Text; 
using System.Threading.Tasks; 

namespace ConsoleApplication54 
{ 
class Program 
{ 
    static void Main(string[] args) 
    { 
     int counter = 0; 
     var sw = new Stopwatch(); 
     unchecked 
     { 
      int sum = 0; 
      while (true) 
      { 
       try 
       { 
        if (counter > 20) 
         throw new Exception("exception"); 
       } 
       catch 
       { 
       } 

       sw.Restart(); 
       for (int i = 0; i < int.MaxValue; i++) 
       { 
        sum += i; 
       } 
       counter++; 
       Console.WriteLine(sw.Elapsed); 
      } 

     } 
    } 
} 
} 

Io sono su una macchina a 64 bit e VS 2015 installato. Quando eseguo il codice sotto 32 bit, viene eseguito ogni iterazione intorno a 0,6 secondi, stampato sulla console. Quando lo eseguo a 64 bit, la durata di ciascuna iterazione salta semplicemente a 4 secondi! Ho provato il codice di esempio nel computer dei miei colleghi che ha installato solo VS 2013. Esistono entrambe le versioni a 32 e 64 bit per lo 0,6 secondi.

Inoltre, se rimuoviamo il blocco di prova, viene eseguito anche in 0,6 secondi con VS 2015 a 64 bit.

Sembra una regressione RyuJIT seria quando c'è un blocco catch try. Ho ragione ?

+0

Il tuo computer è super geniale! per me ci vogliono circa 10 secondi per ogni iterazione :(e nessuna differenza qui, sia a 32 bit che a 64 bit dà i medesimi risultati –

+0

@ M.kazem. Non penso che sia possibile.Il mio computer è un Surface Pro 3 i7 con U CPU di livello.Non è sicuramente una powerhouse.Sei sicuro di eseguirlo in modalità Release e Avvia senza debugging? BTW Ho provato finora 4 computer diversi. –

+0

Oh no no I Ho capito perché hai abilitato l'opzione 'Ottimizza codice 'nelle proprietà della soluzione ora ho' 1.3s' per 32bit e '3.9s' per 64 bit. –

risposta

11

Benchmarking è un'arte raffinata. Apporta una piccola modifica al tuo codice:

Console.WriteLine("{0}", sw.Elapsed, sum); 

E ora vedrai la differenza sparire. O per dirla in un altro modo, la versione x86 ora è altrettanto lenta del codice x64. Probabilmente si può capire cosa RyuJIT non fa quello che ha fatto il jitter eredità da questo piccolo cambiamento, non elimina il superfluo

sum += i; 

Qualcosa si può vedere quando si guarda il codice macchina generato con Test> Windows> Disassembly. Che è davvero una stranezza in RyuJIT. La sua eliminazione del codice morto non è accurata quanto il jitter legacy. Altrimenti non del tutto senza motivo, Microsoft ha riscritto il jitter x64 a causa di bug che non è stato in grado di risolvere facilmente. Uno di questi era un problema abbastanza sgradevole con l'ottimizzatore, non aveva limiti superiori alla quantità di tempo speso per l'ottimizzazione di un metodo. Causando un comportamento piuttosto scadente su metodi con corpi molto grandi, potrebbe essere nel bosco per decine di millisecondi e causare pause di esecuzione notevoli.

Chiamarlo bug, meh, non proprio. Scrivi un codice sano e il jitter non ti deluderà. Ottimizzazione fa inizia per sempre al solito posto, tra le orecchie del programmatore.

0

Dopo un po 'di test ho ottenuto risultati interessanti. I miei test ruotavano attorno al blocco try catch. Come ha sottolineato l'OP, se rimuovi questo blocco, il tempo di esecuzione è lo stesso. L'ho ulteriormente ristretto e ho concluso che è a causa della variabile nell'istruzione if nel blocco try.

Consente rimuovere il ridondante throw:

   try 
       { 
        if (counter== 0) { } 
       } 
       catch 
       { 
       } 

Si otterrà gli stessi risultati con questo codice come avete fatto con il codice originale.

Consente contatore modifica sia un valore int attuale:

   try 
       { 
        if (1 == 0) { } 
       } 
       catch 
       { 
       } 

Con questo codice, la versione a 64 bit è diminuita in tempo di esecuzione da 4 secondi per circa 1,7 secondi. Ancora doppio rispetto alla versione a 32 bit. Comunque ho pensato che fosse interessante.Sfortunatamente dopo la mia rapida ricerca su Google non ho ancora trovato una ragione, ma scaverò un po 'di più e aggiornerò questa risposta se scoprirò perché questo sta accadendo.

Per quanto riguarda il secondo rimanente che vorremmo radere dalla versione a 64 bit, posso vedere che si tratta di aumentare lo sum di i nel ciclo for. Consente cambiare questo modo che sum non superi i suoi limiti:

  for (int i = 0; i < int.MaxValue; i++) 
      { 
       sum ++; 
      } 

Questa modifica (insieme con il cambiamento nel blocco try) ridurrà il tempo di esecuzione dell'app 64 bit per 0,7 secondi. Il mio ragionamento per la differenza di 1 secondo nel tempo è dovuto al modo artificiale in cui la versione a 64 bit deve gestire un int che è naturalmente a 32 bit.

Nella versione a 32 bit, ci sono 32 bit assegnati a Int32 (sum). Quando sum supera i suoi limiti, è facile determinare questo fatto.

Nella versione a 64 bit, ci sono 64 bit allocati a Int32 (sum). Quando la somma supera i suoi limiti, deve esserci un meccanismo per rilevarlo, che potrebbe portare al rallentamento. Forse anche l'operazione di aggiungere sum & i richiede più tempo a causa dell'aumento dei bit ridondanti allocati.

Sto teorizzando qui; quindi non prendere questo come vangelo. Ho solo pensato di pubblicare le mie scoperte. Sono sicuro che qualcun altro sarà in grado di far luce sul problema che ho trovato.

-

Aggiorna

risposta s' @HansPassant indicate che la linea sum += i; può essere eliminato in quanto si ritiene inutile, che rende perfettamente senso, sum non viene utilizzata all'esterno del ciclo for . Dopo aver introdotto il valore della somma al di fuori del ciclo for, abbiamo notato che la versione x86 era altrettanto lenta della versione x64. Quindi ho deciso di fare un po 'di test. Consente di modificare il ciclo e la stampa al seguente:

   int x = 0; 
       for (int i = 0; i < int.MaxValue; i++) 
       { 
        sum += i; 
        x = sum; 
       } 
       counter++; 
       Console.WriteLine(sw.Elapsed + " " + x); 

Si può vedere che ho introdotto un nuovo int x che viene assegnato il valore della sum nel ciclo for. Quel valore di x non viene scritto nella console. sum non lascia il ciclo for. Questo, che ci crediate o no, riduce effettivamente il tempo di esecuzione per x64 a 0,7 secondi. Tuttavia, la versione x86 salta fino a 1,4 secondi.

+0

Non importa se 'sum' supera i suoi limiti - è in esecuzione in un contesto non controllato, quindi l'overflow viene ignorato - se il codice è in esecuzione. Come fa notare Hans, poiché la variabile 'sum' non viene letta dopo questo ciclo, è * possibile * (x86, vecchio x64 xIT) per eliminare completamente il ciclo come ottimizzazione. –

+0

@Damien_The_Unbeliever In realtà hai sollevato un punto molto interessante .. Permettimi di aggiornare la mia risposta con le mie conclusioni. –

+0

@Damien_The_Unbeliever Non penso che il fatto che questo sia deselezionato conta davvero. Ciò significa semplicemente che non verrà lanciata una OverflowException quando la somma raggiunge i suoi limiti. (Diversamente se lo hai modificato in modo controllato). Comunque, ha ancora bisogno di eseguire un controllo artificiale dei limiti a 64 bit perché l'int ha 64 bit assegnati ad esso. In teoria, credo. Ancora una volta, non sono un esperto in questi argomenti, questo mi interessa solo. –

Problemi correlati