perché l'ultima funzione è il 10% più veloce anche se deve creare le variabili più e più volte?

14
var toSizeString = (function() {

 var KB = 1024.0,
     MB = 1024 * KB,
     GB = 1024 * MB;

  return function(size) {
    var gbSize = size / GB,
        gbMod  = size % GB,
        mbSize = gbMod / MB,
        mbMod  = gbMod % MB,
        kbSize = mbMod / KB;

    if (Math.floor(gbSize)) {
      return gbSize.toFixed(1) + 'GB';
    } else if (Math.floor(mbSize)) {
      return mbSize.toFixed(1) + 'MB';
    } else if (Math.floor(kbSize)) {
      return kbSize.toFixed(1) + 'KB';
    } else {
      return size + 'B';
    }
  };
})();

E la funzione più veloce: (nota che deve sempre calcolare sempre le stesse variabili kb / mb / gb). Dove guadagna prestazioni?

function toSizeString (size) {

 var KB = 1024.0,
     MB = 1024 * KB,
     GB = 1024 * MB;

 var gbSize = size / GB,
     gbMod  = size % GB,
     mbSize = gbMod / MB,
     mbMod  = gbMod % MB,
     kbSize = mbMod / KB;

 if (Math.floor(gbSize)) {
      return gbSize.toFixed(1) + 'GB';
 } else if (Math.floor(mbSize)) {
      return mbSize.toFixed(1) + 'MB';
 } else if (Math.floor(kbSize)) {
      return kbSize.toFixed(1) + 'KB';
 } else {
      return size + 'B';
 }
};
    
posta Tomy 15.03.2015 - 14:04
fonte

3 risposte

23

I moderni motori JavaScript fanno tutti la compilazione just-in-time. Non puoi fare supposizioni su ciò che "deve creare più e più volte". Questo tipo di calcolo è relativamente facile da ottimizzare, in entrambi i casi.

D'altra parte, chiudere le variabili costanti non è un caso tipico per cui si dovrebbe scegliere come destinazione la compilazione JIT. Generalmente si crea una chiusura quando si desidera essere in grado di modificare tali variabili in invocazioni diverse. Stai anche creando una deviazione puntatore aggiuntiva per accedere a tali variabili, come la differenza tra l'accesso a una variabile membro e un int locale in OOP.

Questo tipo di situazione spiega perché la gente butta fuori la linea di "ottimizzazione prematura". Le facili ottimizzazioni sono già state fatte dal compilatore.

    
risposta data 15.03.2015 - 15:19
fonte
12

Le variabili sono economiche. Contesti di esecuzione e catene dell'ambito sono costosi.

Ci sono varie risposte che sostanzialmente si riducono a "perché le chiusure", e quelle sono essenzialmente vere, ma il problema non è specificamente con la chiusura, è il fatto che si ha una funzione di riferimento variabili in un ambito diverso. Avresti lo stesso problema se queste fossero variabili globali sull'oggetto window , al contrario delle variabili locali all'interno dell'IFE. Provalo e vedi.

Quindi nella tua prima funzione, quando il motore vede questa affermazione:

var gbSize = size / GB;

Deve seguire i seguenti passi:

  1. Cerca una variabile size nell'ambito corrente. (Trovato.)
  2. Cerca una variabile GB nell'ambito corrente. (Non trovato.)
  3. Cerca una variabile GB nello scope genitore. (Trovato.)
  4. Esegui il calcolo e assegna a gbSize .

Il passaggio 3 è molto più costoso dell'assegnazione di una variabile. Inoltre, fai questo cinque volte , incluso il doppio per GB e MB . Ho il sospetto che se hai eseguito l'aliasing di questi all'inizio della funzione (ad esempio var gb = GB ) e fatto riferimento all'alias, in realtà produrrebbe una piccola accelerazione, sebbene sia anche possibile che alcuni motori JS eseguano già questa ottimizzazione. E, naturalmente, il modo più efficace per accelerare l'esecuzione è semplicemente non attraversare affatto la catena dell'ambito.

Ricorda che JavaScript non è come un linguaggio compilato, tipizzato staticamente, in cui il compilatore risolve questi indirizzi variabili in fase di compilazione. Il motore JS deve risolverli per nome e queste ricerche avvengono sempre in fase di esecuzione. Quindi vuoi evitarli quando possibile.

L'assegnazione variabile è estremamente economica in JavaScript. Potrebbe essere in realtà l'operazione più economica, anche se non ho nulla a sostegno di questa affermazione. Ciononostante, è sicuro dire che è quasi mai una buona idea cercare di evitare di creare variabili; quasi ogni ottimizzazione che cerchi di fare in quella zona finirà per peggiorare le cose, in termini di prestazioni.

    
risposta data 15.03.2015 - 19:49
fonte
2

Un esempio comporta una chiusura, l'altro no. L'implementazione delle chiusure è piuttosto complicata, poiché le variabili chiuse su non funzionano come le variabili normali. Questo è più ovvio in un linguaggio di basso livello come C, ma userò JavaScript per illustrarlo.

Una chiusura non consiste solo in una funzione, ma anche in tutte le variabili su cui si è chiusa. Quando vogliamo invocare questa funzione, dobbiamo anche fornire tutte le variabili chiuse. Possiamo modellare una chiusura con una funzione che riceve un oggetto come primo argomento che rappresenta queste variabili chiuse sopra:

function add(vars, y) {
  vars.x += y;
}

function getSum(vars) {
  return vars.x;
}

function makeAdder(x) {
  return { x: x, add: add, getSum: getSum };
}

var adder = makeAdder(40);
adder.add(adder, 2);
console.log(adder.getSum(adder));  //=> 42

Nota la convenzione di chiamate awkward closure.apply(closure, ...realArgs) che richiede

Il supporto per oggetti incorporati di JavaScript rende possibile omettere l'argomento vars esplicito e ci consente invece di utilizzare this invece:

function add(y) {
  this.x += y;
}

function getSum() {
  return this.x;
}

function makeAdder(x) {
  return { x: x, add: add, getSum: getSum };
}

var adder = makeAdder(40);
adder.add(2);
console.log(adder.getSum());  //=> 42

Questi esempi sono equivalenti a questo codice che utilizza effettivamente le chiusure:

function makeAdder(x) {
  return {
    add: function (y) { x += y },
    getSum: function () { return x },
  };
}

var adder = makeAdder(40);
adder.add(2);
console.log(adder.getSum());  //=> 42

In questo ultimo esempio, l'oggetto è usato solo per raggruppare le due funzioni restituite; il binding di this è irrilevante. Tutti i dettagli per rendere possibili le chiusure - passando i dati nascosti alla funzione reale, cambiando tutti gli accessi alle variabili di chiusura per le ricerche in quei dati nascosti - sono curati dal linguaggio.

Ma chiamare le chiusure implica il sovraccarico di passare quei dati in più, e l'esecuzione di una chiusura comporta il sovraccarico delle ricerche in quei dati extra - aggravata dalla cattiva localizzazione della cache e di solito una dereferenziazione del puntatore rispetto alle variabili ordinarie - così che sia non sorprende che una soluzione che non si basa su chiusure funzioni meglio. Tanto più che tutto ciò che la tua chiusura ti fa risparmiare è un paio di operazioni aritmetiche estremamente economiche, che potrebbero persino essere piegate in modo costante durante l'analisi.

    
risposta data 15.03.2015 - 16:35
fonte

Leggi altre domande sui tag