C #, Per loop e test di velocità ... Esegui lo stesso loop più velocemente una seconda volta?

StackOverflow https://stackoverflow.com/questions/303343

Domanda

public Int64 ReturnDifferenceA()
{
  User[] arrayList;
  Int64 firstTicks;
  IList<User> userList;
  Int64 secondTicks;
  System.Diagnostics.Stopwatch watch;

  userList = Enumerable
              .Range(0, 1000)
              .Select(currentItem => new User()).ToList();

  arrayList = userList.ToArray();

  watch = new Stopwatch();
  watch.Start();

  for (Int32 loopCounter = 0; loopCounter < arrayList.Count(); loopCounter++)
  {
     DoThings(arrayList[loopCounter]);
  }

  watch.Stop();
  firstTicks = watch.ElapsedTicks;

  watch.Reset();
  watch.Start();
  for (Int32 loopCounter = 0; loopCounter < arrayList.Count(); loopCounter++)
  {
     DoThings(arrayList[loopCounter]);
  }
  watch.Stop();
  secondTicks = watch.ElapsedTicks;

  return firstTicks - secondTicks;
}

Come puoi vedere, questo è davvero semplice. Creare un elenco di utenti, forzare un array, avviare un orologio, scorrere l'elenco e chiamare un metodo, cronometro. Ripetere. Termina restituendo la differenza tra la prima e la seconda.

Ora sto chiamando con questi:

differenceList = Enumerable
                 .Range(0, 50)
                 .Select(currentItem => ReturnDifferenceA()).ToList();
average = differenceList.Average();

differenceListA = Enumerable
                  .Range(0, 50)
                  .Select(currentItem => ReturnDifferenceA()).ToList();
averageA = differenceListA.Average();

differenceListB = Enumerable
                  .Range(0, 50)
                  .Select(currentItem => ReturnDifferenceA()).ToList();
averageB = differenceListB.Average();

Ora la parte divertente è che tutte le medie sono positive di una quantità relativamente grande, che va da 150k a 300k tick.

Quello che non capisco è che sto attraversando la stessa lista, allo stesso modo, con lo stesso metodo e tuttavia c'è una tale differenza. Esiste una sorta di memorizzazione nella cache?

Un'altra cosa interessante è che se eseguo l'iterazione dell'elenco PRIMA della prima sezione del cronometro, le medie si aggirano intorno ai 5k circa.

È stato utile?

Soluzione

a proposito, l'uso di IEnumerable.Count () su un array è centinaia di volte più lento di Array.Length ... Anche se questo non risponde affatto alla domanda.

Altri suggerimenti

Stai funzionando in un linguaggio di alto livello con un ambiente di runtime che fa molta cache e ottimizzazioni delle prestazioni, questo è comune. A volte viene chiamato riscaldamento della macchina virtuale o riscaldamento del server (quando si tratta di un'applicazione di produzione).

Se qualcosa deve essere fatto ripetutamente, allora noterai spesso la prima volta che ha un tempo di misurazione più grande e il resto dovrebbe livellarsi a una quantità inferiore.

Faccio questo nel codice MATLAB e vedo che la prima volta che eseguo un ciclo di benchmark, ci vogliono cinque secondi e i tempi successivi impiegano un quinto di secondo. È una grande differenza, perché è un linguaggio interpretato che richiede una qualche forma di compilazione, ma in realtà non influisce sulle tue prestazioni, perché la grande maggioranza sarà "la seconda volta" in qualsiasi applicazione di produzione.

È del tutto possibile che DoThings () non sia compilato JIT in codice nativo fino alla prima volta che viene chiamato.

Perché .NET, come la piattaforma Java, è un ambiente JIT. Tutto il codice .NET di alto livello è compilato con il bytecode del linguaggio intermedio di Microsoft.

Per eseguire il programma, questo bytecode deve essere compilato / tradotto in codice macchina nativo. Tuttavia, i file programmati .NET compilati non sono memorizzati nel codice macchina nativo ma nel bytecode intermedio della macchina virtuale.

La prima esecuzione è compilata JIT, quindi ci è voluto del tempo extra. Le esecuzioni successive non devono più essere compilate JIT ma il codice nativo viene estratto dalla cache JIT, quindi dovrebbe essere più veloce.

Hai mantenuto la tua applicazione attiva senza interrompere le esecuzioni successive? Quindi, il secondo motivo è dovuto anche alla VM. (VM: 1 = macchina virtuale; VM: 2 = memoria virtuale). Tutti i moderni sistemi operativi generalizzati eseguono i loro processi sulla memoria virtuale, che è una mappa della memoria reale, per consentire al sistema operativo di gestire e ottimizzare l'uso delle risorse di sistema. I processi meno utilizzati vengono spesso trasferiti nella cache del disco per consentire ad altri processi di utilizzare in modo ottimale le risorse.

Il tuo processo non era nella memoria virtuale per la prima volta, quindi deve subire il sovraccarico di essere trascinato nella memoria. Poiché in seguito, il processo è stato tra i primi nell'elenco utilizzato più di recente (ovvero nella parte inferiore dell'elenco utilizzato meno di recente), non è stato ancora trasferito nella cache del disco.

Inoltre, le risorse vengono assegnate dal sistema operativo al processo in base alle esigenze. Quindi, per il primo round, il tuo processo ha dovuto affrontare il problema di spingere la contesa dell'involucro con il sistema operativo per espandere i suoi limiti di risorse.

Una macchina virtuale consente a .NET e Java di astrarre la maggior parte delle funzionalità di programmazione in un livello indipendente dalla macchina, segregando e quindi lasciando un disordine minore per gli ingegneri del software dipendenti dalla macchina. Anche se Microsoft Windows funziona su hardware discendente x86 piuttosto unificato, esistono differenze sufficienti con le diverse versioni del sistema operativo e modelli di CPU per garantire una macchina virtuale astratta al fine di offrire ai programmatori e agli utenti .NET una visione coerente.

Dici di non farlo 3 volte, la seconda e la terza volta sono relativamente vicine. Mi sembra che sia solo la prima volta attraverso il ciclo che le cose sono lente.

Sospetto che la funzione che chiami non sia Just-In-Timed fino alla prima esecuzione. Quello che puoi provare è eseguirlo una volta, quindi arrestarlo ed eseguirlo di nuovo. Senza modifiche al codice, le compilazioni Just-In-Time della precedente esecuzione dovrebbero essere ancora valide e tutte le ottimizzazioni rimanenti visualizzate sono gli effettivi effetti di memorizzazione nella cache sul lavoro.

Lascia da parte la questione del riscaldamento della VM o della macchina, della memorizzazione nella cache, delle ottimizzazioni JIT, per un momento: cos'altro sta facendo il tuo computer? Qualcuno dei servizi di sistema 3e42 e le cose sulla barra delle applicazioni stanno afferrando un po 'di CPU? Forse il tuo client Steam ha deciso di verificare la presenza di aggiornamenti o IE doveva fare qualcosa di spaventosamente importante o il tuo programma antivirus si è messo in mezzo?

Il test è utile solo quanto è possibile isolarlo da tutti gli altri software in esecuzione sulla confezione. Disattiva ogni bit di software possibile prima di provare a misurare una corsa.

Ma allora cosa so? - forse il tuo metodo di misurazione è gestito anche dal runtime .net (o altro) e tiene conto solo dei "cicli virtuali" di runtime.

Autorizzato sotto: CC-BY-SA insieme a attribuzione
Non affiliato a StackOverflow
scroll top