2010-04-07 9 views
13

(sfondo: Why should I use int instead of a byte or short in C#)int, a breve, le prestazioni di byte in back-to-back for-loop

per soddisfare la mia curiosità circa i pro ei contro di utilizzare l'intero "dimensione appropriata" contro il "ottimizzato" intero ho scritto il seguente codice che ha rafforzato ciò che in precedenza ritenevo vero per le prestazioni int in .Net (e che è spiegato nel link sopra) che è che è ottimizzato per le performance int piuttosto che short o byte.

DateTime t; 
long a, b, c; 

t = DateTime.Now; 
for (int index = 0; index < 127; index++) 
{ 
    Console.WriteLine(index.ToString()); 
}   
a = DateTime.Now.Ticks - t.Ticks; 

t = DateTime.Now; 
for (short index = 0; index < 127; index++) 
{ 
    Console.WriteLine(index.ToString()); 
} 

b=DateTime.Now.Ticks - t.Ticks; 

t = DateTime.Now;   
for (byte index = 0; index < 127; index++) 
{ 
    Console.WriteLine(index.ToString()); 
} 
c=DateTime.Now.Ticks - t.Ticks; 

Console.WriteLine(a.ToString()); 
Console.WriteLine(b.ToString()); 
Console.WriteLine(c.ToString()); 

Questo dà risultati più o meno consistenti nel settore della ...

~ 950000

~ 2000000

~ 1700000

che è in linea con quello che mi aspetterei di vedere.

Tuttavia quando provo a ripetere i cicli per ogni tipo di dati come questo ...

t = DateTime.Now; 
for (int index = 0; index < 127; index++) 
{ 
    Console.WriteLine(index.ToString()); 
} 
for (int index = 0; index < 127; index++) 
{ 
    Console.WriteLine(index.ToString()); 
} 
for (int index = 0; index < 127; index++) 
{ 
    Console.WriteLine(index.ToString()); 
} 
a = DateTime.Now.Ticks - t.Ticks; 

i numeri sono più come ...

~ 4500000

~ 3100000

~ 300000

Che trovo sconcertante. Qualcuno può offrire una spiegazione?

NOTA: Nell'interesse del confronto come per come ho limitato i loop a 127 a causa della gamma del byte di tipo valore. Anche questo è un atto di curiosità non di micro-ottimizzazione del codice di produzione.

+1

'byte' ha la gamma di 0-255. Non è un tipo di dati firmato. –

+6

Inoltre, la classe 'DateTime' non è adatta per il profiling delle prestazioni di basso livello. Usa 'System.Diagnostics.Stopwatch'. –

+0

@'Aaronaught, Jon: Grazie per la soluzione. Ho alcuni chiarimenti ... indice <255/127; ... In questo codice, 255/127 è sempre il tipo di dati Byte/Short/Int. O.Net IL cambierà il suo tipo di dati di 255/127 per indicizzare il tipo di dati per rispettivi cicli? Possiamo dichiarare la costante per il rispettivo tipo di dati for-loop e check-it? – Thulasiram

risposta

35

Prima di tutto, non è .NET ottimizzata per int prestazioni, è la macchina che è ottimizzato per i 32 bit è la dimensione della parola nativa (a meno che non siete su x64, nel qual caso si tratta di long o 64 bit) .

In secondo luogo, si sta scrivendo alla console all'interno di ogni ciclo: anche questo è molto più costoso dell'incremento e del test del contatore di loop, quindi non si sta misurando nulla di realistico qui.

In terzo luogo, un intervallo byte ha un intervallo fino a 255, quindi è possibile eseguire il ciclo 254 volte (se si tenta di eseguire 255, l'overflow non termina mai, ma non è necessario arrestarsi a 128).

In quarto luogo, non stai facendo ovunque vicino a iterazioni sufficienti per il profilo. L'iterazione di un ciclo stretto a 128 o anche a 254 volte non ha senso. Quello che dovresti fare è mettere il ciclo byte/short/int all'interno di un altro ciclo che itera un numero molto più grande di volte, diciamo 10 milioni, e controlla i risultati.

Infine, l'utilizzo di DateTime.Now nei calcoli determinerà un certo "disturbo" dei tempi durante la creazione di profili. È consigliabile (e più semplice) utilizzare la classe Stopwatch.

In conclusione, questo ha bisogno di molte modifiche prima che possa essere un test perfetto.


Ecco quello che mi considero un più accurato programma di test:

class Program 
{ 
    const int TestIterations = 5000000; 

    static void Main(string[] args) 
    { 
     RunTest("Byte Loop", TestByteLoop, TestIterations); 
     RunTest("Short Loop", TestShortLoop, TestIterations); 
     RunTest("Int Loop", TestIntLoop, TestIterations); 
     Console.ReadLine(); 
    } 

    static void RunTest(string testName, Action action, int iterations) 
    { 
     Stopwatch sw = new Stopwatch(); 
     sw.Start(); 
     for (int i = 0; i < iterations; i++) 
     { 
      action(); 
     } 
     sw.Stop(); 
     Console.WriteLine("{0}: Elapsed Time = {1}", testName, sw.Elapsed); 
    } 

    static void TestByteLoop() 
    { 
     int x = 0; 
     for (byte b = 0; b < 255; b++) 
      ++x; 
    } 

    static void TestShortLoop() 
    { 
     int x = 0; 
     for (short s = 0; s < 255; s++) 
      ++x; 
    } 

    static void TestIntLoop() 
    { 
     int x = 0; 
     for (int i = 0; i < 255; i++) 
      ++x; 
    } 
} 

Questo viene eseguito ogni loop all'interno di un ciclo molto più grande (5 milioni di iterazioni) ed esegue un'operazione molto semplice all'interno del loop (incrementa una variabile). I risultati per me erano:

Byte loop: Tempo trascorso = 00: 00: 03.8949910
breve loop: Tempo trascorso = 00: 00: 03.9098782
Int loop: Tempo trascorso = 00: 00: 03,2986990

Quindi, nessuna differenza apprezzabile.

Inoltre, assicurati di avere il profilo in modalità di rilascio, molte persone dimenticano e eseguono il test in modalità di debug, che sarà significativamente meno preciso.

+1

Ooh grazie, non ho mai veramente provato a profilare il mio codice prima. Buoni punti, presi a bordo :) – gingerbreadboy

+2

@Aaronaught: Mi piace quanto simili siano i nostri benchmark :) –

+2

@Jon: Giuro che non ho copiato i tuoi. : P – Aaronaught

11

La maggior parte di questo tempo è probabilmente dedicata alla scrittura sulla console. Prova a fare qualcosa di diverso da quello nel ciclo ...

Inoltre:

  • Utilizzando DateTime.Now è un brutto modo di misurare il tempo. Utilizzare System.Diagnostics.Stopwatch invece
  • Una volta eliminata la chiamata Console.WriteLine, un ciclo di 127 iterazioni sarà troppo breve per essere misurato. È necessario eseguire il ciclo lotti di volte per ottenere una misurazione sensata.

Ecco il mio punto di riferimento:

using System; 
using System.Diagnostics; 

public static class Test 
{  
    const int Iterations = 100000; 

    static void Main(string[] args) 
    { 
     Measure(ByteLoop); 
     Measure(ShortLoop); 
     Measure(IntLoop); 
     Measure(BackToBack); 
     Measure(DelegateOverhead); 
    } 

    static void Measure(Action action) 
    { 
     GC.Collect(); 
     GC.WaitForPendingFinalizers(); 
     GC.Collect(); 
     Stopwatch sw = Stopwatch.StartNew(); 
     for (int i = 0; i < Iterations; i++) 
     { 
      action(); 
     } 
     sw.Stop(); 
     Console.WriteLine("{0}: {1}ms", action.Method.Name, 
          sw.ElapsedMilliseconds); 
    } 

    static void ByteLoop() 
    { 
     for (byte index = 0; index < 127; index++) 
     { 
      index.ToString(); 
     } 
    } 

    static void ShortLoop() 
    { 
     for (short index = 0; index < 127; index++) 
     { 
      index.ToString(); 
     } 
    } 

    static void IntLoop() 
    { 
     for (int index = 0; index < 127; index++) 
     { 
      index.ToString(); 
     } 
    } 

    static void BackToBack() 
    { 
     for (byte index = 0; index < 127; index++) 
     { 
      index.ToString(); 
     } 
     for (short index = 0; index < 127; index++) 
     { 
      index.ToString(); 
     } 
     for (int index = 0; index < 127; index++) 
     { 
      index.ToString(); 
     } 
    } 

    static void DelegateOverhead() 
    { 
     // Nothing. Let's see how much 
     // overhead there is just for calling 
     // this repeatedly... 
    } 
} 

E i risultati:

ByteLoop: 6585ms 
ShortLoop: 6342ms 
IntLoop: 6404ms 
BackToBack: 19757ms 
DelegateOverhead: 1ms 

(Questo è su un netbook - regolare il numero di iterazioni fino ad ottenere qualcosa di sensato :)

Ciò sembra dimostrarlo rendendo sostanzialmente non significativo il tipo che usi.

+0

ma tutti i loop scrivono sulla console lo stesso numero di volte, ovvero 127 x n-loop – gingerbreadboy

+0

sebbene suppongo che int.toString() possa impiegare più tempo di byte.toString() forse? – gingerbreadboy

+5

@runrunraygun: 'Console.WriteLine' è un'operazione asincrona con un tempo di esecuzione inaffidabile. Mentre non è eccessivamente probabile che avrebbe un effetto drammatico sui risultati, utilizzare qualcosa di più affidabile. Inoltre, 'int.ToString()' non ha la stessa funzione di 'byte.ToString()', quindi non stai eseguendo la stessa azione in ogni ciclo. –

0

Creazione di profili. Il codice Net è molto complicato poiché l'ambiente runtime in cui è in esecuzione il codice byte compilato può eseguire ottimizzazioni di runtime sul codice byte.Nel tuo secondo esempio, il compilatore JIT probabilmente ha individuato il codice ripetuto e ha creato una versione più ottimizzata. Ma senza una descrizione veramente dettagliata di come funziona il sistema run-time, è impossibile sapere cosa sta per succedere al tuo codice. Sarebbe sciocco tentare di indovinare in base alla sperimentazione dato che Microsoft ha perfettamente i diritti di riprogettare il motore JIT in qualsiasi momento, a condizione che non interrompano alcuna funzionalità.

+0

L'esecuzione del codice all'interno del debugger (o, più precisamente, la compilazione e l'esecuzione con le impostazioni predefinite per il profilo Debug con cui viene creato un progetto VS) elimina completamente la possibilità del tipo di ottimizzazione di cui si sta parlando. –

+0

@Adam: Ma chi avrebbe eseguito il codice con un debugger. Ho notato che in VS2005 il codice viene eseguito molto più lentamente all'interno del debugger che stand alone. IIRC, qualcuno qui ha menzionato che l'output del compilatore .net di debug e del compilatore .net di rilascio erano quasi identici ed era il fatto che il codice veniva eseguito stand-alone rispetto al debugger che faceva la differenza. – Skizz

+0

Disabilitare le ottimizzazioni (che è fatto di default nella configurazione di Debug) è specificamente ciò che elimina il tipo di "ottimizzazione di distanza" di cui si sta parlando. Allegare * qualsiasi * debugger può avere un effetto negativo sulle prestazioni, ma questo è un problema diverso. Gli output del compilatore con le ottimizzazioni abilitate sono, infatti, diversi dall'output con le ottimizzazioni disattivate. –

1

Ho provato i due programmi sopra perché sembravano che avrebbero prodotto risultati diversi e potenzialmente in conflitto sulla mia macchina di sviluppo.

uscite dal test harness Aaronaughts'

Short Loop: Elapsed Time = 00:00:00.8299340 
Byte Loop: Elapsed Time = 00:00:00.8398556 
Int Loop: Elapsed Time = 00:00:00.3217386 
Long Loop: Elapsed Time = 00:00:00.7816368 

interi sono molto più veloce

uscite da Jon

ByteLoop: 1126ms 
ShortLoop: 1115ms 
IntLoop: 1096ms 
BackToBack: 3283ms 
DelegateOverhead: 0ms 

nulla in esso

Jon ha la grande costante fissa di chiamare tostring nei risultati che potrebbero nascondere il possibile beneficio s che potrebbe verificarsi se il lavoro svolto nel ciclo era inferiore. Aaronaught utilizza un sistema operativo a 32 bit che non sembra beneficiare dell'utilizzo di inte tanto quanto il rig x64 che sto usando.

Hardware/Software I risultati sono stati raccolti su un Core i7 975 a 3,33 GHz con Turbo disabili e l'affinità di base impostato per ridurre l'impatto di altri compiti. Le impostazioni delle prestazioni sono tutte impostate al massimo e lo scanner antivirus/le attività in background non necessarie sono state sospese. Windows 7 x64 ultimate con 11 GB di RAM di riserva e pochissima attività di IO. Esegui in release config builder vs 2008 senza un debugger o un profiler allegato.

ripetibilità originale ripetuto 10 volte ordine di esecuzione per ogni test che cambiano. La variazione era trascurabile, quindi ho pubblicato solo il mio primo risultato. Sotto il carico massimo della CPU, il rapporto tra i tempi di esecuzione rimaneva costante. Ripetere gira su più blade x64 Xeon XP dà più o meno gli stessi risultati dopo aver preso in considerazione la generazione di CPU e Ghz

Profiling Redgate/JetBrains/Slimtune/profiler CLR e il mio profiler tutti indicano che i risultati siano corretti.

Debug Build L'utilizzo delle impostazioni di debug in VS fornisce risultati coerenti come quelli di Aaronaught.

+0

Sto eseguendo una scatola x64. Questo è un risultato piuttosto anomalo per il primo test: sembra che le versioni 'short' e' byte' abbiano impiegato molto più tempo del dovuto, mentre la versione 'int' era molto vicina alla mia. Hai eseguito il test alcune volte? Hai qualcos'altro in esecuzione allo stesso tempo? – Aaronaught

+0

Hai provato a riordinare i loop int byte brevi per vedere se c'è qualche differenza? Nel caso in cui il compilatore JIT stia decidendo che un terzo ciclo potrebbe valerne l'ottimizzazione in quanto sembra un'operazione comune. Solo un pensiero. Sarebbe interessante da vedere. – Skizz

+0

@Aaronaught Passando il mio config alle x86 dll ho pareggiato i miei risultati. Ecco perché presumevo che stavate usando un sistema operativo a 32 bit. – Steve

0

La scrittura della console non ha nulla a che fare con le prestazioni effettive dei dati. Ha più a che fare con l'interazione con le chiamate alla libreria della console. Suggerisco di fare qualcosa di interessante all'interno di questi loop che è indipendente dalla dimensione dei dati.

Suggerimenti: turni bit, moltiplica, la gestione di array, inoltre, molti altri ...

4

Solo per curiosità ho modificato un pò il programma da Aaronaught e compilato in entrambe le modalità x86 e x64. Strano, Int funziona molto più veloce in 64:

x86

Byte loop: Tempo trascorso = 00: 00: 00.8636454
breve loop: tempo trascorso = 00:00:00.8795518
USHORT loop: Tempo trascorso = 00: 00: 00.8630357
Int loop: Tempo trascorso = 00: 00: 00.5184154
UInt loop: Tempo trascorso = 00: 00: 00.4950156
lungo loop: Tempo trascorso = 00: 00: 01.2941183
Ulong loop: Tempo trascorso = 00: 00: 01,3023409

x64

Byte loop: Tempo trascorso = 00: 00: 01.0646588
breve loop: Tempo trascorso = 00:00: 01.0719330
USHORT loop: Tempo trascorso = 00: 00: 01.0711545
Int loop: Tempo trascorso = 00: 00: 00.2462848
UInt loop: Tempo trascorso = 00: 00: 00.4708777
lungo loop: tempo trascorso = 00:00 : 00.5242272
Ulong loop: Tempo trascorso = 00: 00: 00,5144035