Qual è il modo migliore per bilanciare le applicazioni javascript? [chiuso]

4

Scrivo molte applicazioni javascript e in molte circostanze il browser non risponderà o darà un errore di "slow script". Anche seguendo best practice, inizializzando set di dati di grandi dimensioni, animazioni complesse o quando troppi gestori di eventi si attivano contemporaneamente, devo includere extra setTimeouts o requestAnimationFrames attorno ai blocchi di script per bilanciare il carico. Sembra che ci dovrebbe essere un modo standard di gestire il carico del browser per applicazioni javascript di grandi dimensioni.

Qualche idea? Sembra che ci debbano essere i progettisti di applicazioni JS a pensarci, ma non trovo nulla sul Web o sullo stack.

modifica

Apprezzo tutte le risposte. La mia domanda è non "Come posso scrivere Javascript efficiente?" che sembra essere la domanda che le risposte attuali stanno rispondendo. La mia domanda è: come posso bilanciare il carico di più thread sequenziali di javascript nell'esecuzione nello stesso browser?

Sono ben consapevole di tutte le informazioni pubblicate nelle risposte finora e apprezzo i poster, ma sto cercando un framework o un modello di progettazione che bilancia l'esecuzione degli script in un browser. Questa domanda è comune in molte altre lingue e per gli sviluppatori, sto semplicemente facendo la stessa domanda per javascript.

    
posta BishopZ 03.05.2013 - 16:50
fonte

9 risposte

13

Hai un arco di tempo molto breve tra le tue mani.

Prima di parlare dei thread:

Il DOM è lento

Davvero.

Se il tuo sito web è DOM-pesante, quindi trovare modi per offshore queste manipolazioni. Usa le transizioni CSS3, usa il canvas per le animazioni, evita vernici non necessarie , ecc.

Esistono anche tecniche per renderlo più veloce, come DocumentFragments, utilizzando elementi fuori DOM, ecc.

JavaScript viene eseguito in un singolo thread nel browser

Questo significa che i tuoi script devono essere molto rapidi se vuoi che l'utente non provi alcun tipo di ritardo.

È possibile, ad esempio, interrompere calcoli intensivi in piccoli frammenti, per (un semplice esempio):

function findMax(arr){
   var currentMax = -Infinity;
   var i=0;
   setImmidiate(function() doWork{
       if(arr[i] > currentMax){
           currentMax = arr[i];
       }
       i++;
       if(i< arr.length){
           setImmidiate(doWork);
       }
   });
   return currentMax;
};

Aspetta, non andare ancora! Ho mentito! Non è realmente single threaded

JavaScript non deve più eseguire thread singoli nel browser! Se supporti i browser moderni, puoi utilizzare WebWorkers. . Ciò consentirà di eseguire script intensivi nei thread in background e non interrompere il flusso del programma principale.

Se supporti i browser moderni, questo è probabilmente il modo corretto di gestire i calcoli intensivi della CPU.

I Web Worker ti permettono di usare i thread a cui sei abituato da altri linguaggi, ma in un modo di salvataggio (attore-sistema '). Puoi dividere il lavoro tra i lavoratori e bilanciarlo come faresti in qualsiasi altro linguaggio di programmazione.

Nota i web worker richiedono IE10, Safari 4+, Opera 10.6 +, Chrome 3+ o Firefox 3.5 +

Un'ultima cosa

JavaScript è progettato per eseguire I / O asincroni, mentre questo potrebbe non essere quello che stai facendo più spesso di quanto i problemi di prestazioni JavaScript potrebbero essere il risultato del codice sincrono in cui il codice asincrono è appropriato. La gestione sincrona degli eventi, il blocco del codice e più in generale tutto ciò che non è ad uso intensivo della CPU dovrebbe richiedere pochissimo tempo e non richiedere l'uso di "big gun" come i web worker.

    
risposta data 03.05.2013 - 17:14
fonte
5

Non dovresti scrivere JavaScript sincronizzato. JavaScript è single threaded nel browser. Quello che stai facendo non è il bilanciamento del carico - vedi Load Balancing .

Quali migliori pratiche stai usando? JavaScript è piuttosto veloce nei browser moderni e, se stai utilizzando anche le più semplici best practice, a meno che tu non stia facendo qualcosa di incredibilmente complesso, dovresti stare bene.

    
risposta data 03.05.2013 - 17:00
fonte
3

Se uno script è in esecuzione lenta (assumendo il codice lato client qui), in genere non è verso il basso per JS essere lento. I tuoi colli di bottiglia sono molto più probabili da trovare da qualche altra parte. L'API DOM notoriamente lenta, ad esempio.
Non appena parli di manipolazioni DOM (come l'animazione), quasi certamente noterai un calo di velocità.

Un sacco di persone hanno (e continuano a) criticare le API DOM per essere mal progettate, eccessivamente complesse a volte e lentezza verso il basso.
Il problema principale con l'API DOM è che non è controllato da ECMA, come JavaScript, ma da W3. In parole semplici: lato client JS è spesso composto per il 60-75% da manipolazioni DOM, ma per quelle JS fa affidamento su un'API mantenuta e sviluppata da una terza parte. Penso che sia abbastanza ovvio che è solo una ricetta per slow-food.

Tuttavia, se hai bisogno di eseguire molti calcoli lunghi e complessi, un Worker ti consente di (ordinare) generare un thread in background, che genera un evento al completamento.

Un altro collo di bottiglia che può essere evidente è l'uso eccessivo di librerie / strumenti come jQuery. Questi spesso non sono molto modulari in termini di consentire di includere solo quei bit di cui si ha realmente bisogno e di portare con sé un ulteriore sovraccarico. Confronta

document.getElementById('foo');//calls DOM API

a

$('#foo');//calls jQuery init, a few if's and else's to make up what to do
          //then string is passed to querySelector (DOM API), 
          //new jQ object is constructed (which also constructs an array)
          //return new jQObject

Almeno, è questo che succede dietro le quinte di jQuery.
Mentre sono in tema di jQ: un sacco di codice jQ lega effettivamente troppi listener di eventi, portandoci a un terzo collo di bottiglia possibile (e, per ora, ultimo).

Gli ascoltatori di eventi sono controllati in un ciclo infinito, come probabilmente saprai. Se hai 1 ascoltatore, non noterai alcuna perdita di prestazioni. Se si vincolano i 1000 singoli gestori direttamente ai riferimenti ai singoli nodi, lo si farà. Ogni riferimento è tenuto in memoria, anche tutti i gestori (oggetti di costruzione).
La delega degli eventi non è così difficile e può rendere il ciclo degli eventi molto più veloce, come ho scoperto qui

    
risposta data 03.05.2013 - 17:24
fonte
3

Oltre a qualsiasi tecnica specifica, i moderni strumenti di sviluppo del browser hanno tutti profiler JavaScript. Accendili e esegui alcune attività e scopri quali funzioni richiedono più tempo. Attacca ogni collo di bottiglia uno alla volta.

    
risposta data 03.05.2013 - 19:27
fonte
1

Come molti altri hanno già detto, non puoi davvero "caricare il saldo" in javascript. A meno che tu non stia usando lavoratori, javascript fa una cosa alla volta. Il meglio che puoi sperare di fare è lavorare per ottenere le attività fuori dalla coda più velocemente di quanto stanno arrivando. Ecco alcune cose da considerare:

Fai attenzione con il DOM

Una grande parte di questo è, come le altre risposte, evitando i ripetuti del DOM, poiché possono essere estremamente costosi. Prova a fare tutte le tue letture DOM in un unico passaggio e tutte le tue scritture in un altro, poiché la lettura dal DOM spesso lo induce a disegnare tutte le modifiche in coda per assicurarti che abbia informazioni aggiornate. Se stai utilizzando le informazioni per apportare più modifiche al DOM, verrà ridisegnato di nuovo e l'utente non vedrà nemmeno quel primo ridisegno.

Evita setInterval

Se la funzione di callback impiega più tempo dell'intervallo da eseguire, si otterranno più copie della funzione di callback nella coda e il codice scenderà sempre più indietro mentre scorre. Questo è il motivo per cui molte persone suggeriscono di usare un setTimeout che si imposta quando è finito. In questo modo, c'è sempre un'istanza della richiamata in attesa in coda.

Eventi "Burst" di buffer

Alcuni eventi sparano più volte contemporaneamente, ma in realtà dovresti reagire solo all'ultimo evento. Un esempio di ciò che ho visto spesso è un listener di eventi di scroll. Quando l'utente scorre la pagina, questo evento può sparare più volte al secondo, e se il listener fa un vero lavoro, allora la coda si sta accumulando molto più velocemente di quanto non lo sia. Ecco perché vedi spesso schemi come questo:

var scrollTimer;

function scrollHandler(e) {
    // Actually handle the scrolling, DOM manipulations, real work...
}

window.addEventListener('scroll', function (e) {
    clearTimeout(scrollTimer);
    scrollTimer = setTimeout(scrollHandler, 100, e);
});

In questo modo, indipendentemente da quanto l'utente scorre, il codice pesante non viene effettivamente eseguito finché non smettono di scorrere (in questo caso per un decimo di secondo).

Eventi continui cache

Ho scoperto che in alcune situazioni alcuni eventi arrivano quasi continuamente e hanno più ascoltatori. Se stai reagendo all'accelerometro, al GPS, ai controller di gioco (o alle tastiere utilizzate come controller), probabilmente gli eventi arriveranno il più velocemente possibile, innescando i loro ascoltatori quasi costantemente. Ogni cosa cerca di accadere il più velocemente possibile, e questo lo rende lento . A differenza degli eventi "scoppiati", tuttavia, non puoi semplicemente aspettare che questi eventi si fermino prima di reagire. L'intero punto è reagire mentre cambiano.

Se ci pensi, però, il tuo codice deve solo reagire una volta per frame. Avere un singolo keydown / keyup listener che imposta i flag per le chiavi che si desidera. Avere un unico aggiornamento per l'ascoltatore dell'accelerometro e un array con dati di orientamento, oppure un ascoltatore GPS memorizza la latitudine e la longitudine. Questi ascoltatori dovrebbero essere abbastanza veloci da mantenere lo svuotamento della coda tanto rapidamente quanto si riempie. Con quello in atto, puoi passare in rassegna tutti i tuoi oggetti una volta sola e farli controllare i flag e gli array per le informazioni di cui hanno bisogno.

    
risposta data 09.05.2013 - 08:00
fonte
1

Quindi, in base alle tue modifiche, leggo questo come chiedendo "come faccio a dividere in modo efficace un calcolo a lungo termine in più web worker". Ho scritto una libreria per trattare con i dipendenti del web ed ecco cosa ho scoperto, ci sono un paio di modi in cui puoi fare ciò che spesso dipende sui dati. Supponendo che tu abbia un array che vuoi elaborare usando n worker.

  1. Puoi dividere arbitrariamente l'array in n parti e inviare una parte a ciascun lavoratore. Sia tagliando l'array (se è piccolo) o iterando l'elenco e inviando ogni porzione di dati a un altro worker (tramite Math.random () o%). Se i tuoi elementi di dati impiegano una quantità di tempo abbastanza uniforme per elaborarli, sarà veloce come una coda ma non avrà il sovraccarico.
  2. È possibile impostare una coda in modo che dopo aver elaborato un dato, il lavoratore rispedisca il risultato e ottenga il pezzo successivo. Questo bilancia il carico abbastanza bene e significa che se un pezzo di dati richiede 500ms e il resto richiede 1ms non avrai dati in attesa dietro il pezzo da 500ms. Ma ora i lavoratori sono in attesa di ottenere i propri dati e altre complessità aggiuntive che possono portare a tempi più lenti dei primi tipi di coda, ma anche più veloci in altri, soprattutto se si utilizzano oggetti trasferibili per accelerare i tempi di trasferimento.
  3. Puoi farlo da solo. Questa non è in realtà una soluzione alla domanda che hai posto, ma potrebbe essere una soluzione al problema che hai, trovo che sia davvero una buona idea ricontrollare che il sovraccarico di dividere i tuoi dati in più parti non superi quello benefici che ottieni Soprattutto considerando che dal momento che Chrome non supporta i lavoratori che fanno più lavoratori devi fare la divisione nel thread DOM.
risposta data 28.05.2013 - 14:36
fonte
1

Il termine bilanciamento del carico è confuso qui e penso che abbia portato ad alcune risposte che non ritieni utili.

Prima di tutto, tutti i consigli dati nelle altre risposte sono validi. Si prega di ascoltare gli altri esperti qui.

Ora vai al bilanciamento del carico. Bilanciamento del carico significa creare più lavoratori e distribuirli in modo trasparente. Tradizionalmente, significa che più server servono le richieste dei client. Ma vedo come si applica al tuo problema di prestazioni JavaScript qui.

Sembra che stiate cercando di vedere se è possibile suddividere il codice in più processi o thread in modo che possano essere eseguiti in parallelo. Due problemi con questo:

  1. Parallelizzare qualcosa non lo accelera in modo così drammatico come si potrebbe pensare e aggiunge complessità / rischio e talvolta anche penalizzazioni delle prestazioni mentre si aggiungono i punti di sincronizzazione attorno a sezioni critiche .
  2. JavaScript è a thread singolo. Anche se vuoi correre il rischio di creare più thread, non puoi.

Detto questo, c'è una possibile soluzione. C'è una nuova tecnologia chiamata web worker progettata per il trasferimento e la gestione dei messaggi. Questo probabilmente ti può dare quello di cui hai bisogno. Tuttavia, non credo (potrebbe essere errato qui) che è supportato da tutti i browser.

link

Il sito per sviluppatori di Mozilla ha una buona informazione sui problemi di sicurezza dei thread. Leggilo prima di seguire questo corso.

link

    
risposta data 29.05.2013 - 03:39
fonte
0

Quindi ci sono alcune risposte prolisse qui che sono probabilmente tutte corrette. Tuttavia, per il tuo sito specifico, ritengo piuttosto probabile che esistano da 1 a 5 funzioni che rubano il 99% del tempo della CPU e potrebbero essere ottimizzate all'80% con un po 'di lavoro.

La maggior parte dei browser ha strumenti di profilazione del codice che ti permetteranno di guardare il codice mentre fai qualcosa di lento, e poi ti diranno che cosa il browser ha speso più tempo a fare. Qualunque cosa sia in cima alla lista in seguito è ciò su cui dovresti concentrarti per prima.

Le cose sui web worker, ad esempio, sono davvero accurate. Ma, in una pagina web di base che usa solo Javascript per un'esperienza utente migliore (al contrario di fare una sorta di lavoro effettivo) sarei sorpreso se fosse necessario.

    
risposta data 28.05.2013 - 15:19
fonte
0

Per affrontare l'aspetto del "bilanciamento del carico" della tua domanda, direi che è impossibile per il JavaScript basato su browser.

"Load Balancing" implica che hai più di una macchina contro cui lavorare. Cioè se si distribuisce un servizio RESTful, ci sarebbero molte macchine che eseguono lo stesso servizio e diffondere il traffico tra di loro. Questa idea in realtà non si applica a JavaScript in esecuzione nei browser (node.js è una storia diversa) perché hai solo un computer client. Non esiste un modo per distribuire il carico su altre macchine.

"Threading" è probabilmente un'analogia migliore (come altri hanno menzionato). In generale, il threading non è realmente disponibile in JavaScript, ma puoi utilizzare HTML5 Web Worker per ottenere risultati simili ... ma in questo caso sei limitato ai browser supportati.

    
risposta data 29.05.2013 - 01:21
fonte

Leggi altre domande sui tag