Come migliorare significativamente le prestazioni di Java?

23

Il team di LMAX ha una presentazione su come sono stati in grado di fare 100k TPS con meno di 1 ms di latenza . Hanno eseguito il backup di quella presentazione con un blog , scheda tecnica (PDF) e il codice sorgente stesso.

Recentemente Martin Fowler ha pubblicato un ottima carta sull'architettura LMAX e afferma che ora sono in grado di gestire sei milioni di ordini al secondo e mette in evidenza alcuni dei passi che il team ha intrapreso per aumentare di un altro ordine di grandezza nelle prestazioni.

So far I've explained that the key to the speed of the Business Logic Processor is doing everything sequentially, in-memory. Just doing this (and nothing really stupid) allows developers to write code that can process 10K TPS.

They then found that concentrating on the simple elements of good code could bring this up into the 100K TPS range. This just needs well-factored code and small methods - essentially this allows Hotspot to do a better job of optimizing and for CPUs to be more efficient in caching the code as it's running.

It took a bit more cleverness to go up another order of magnitude. There are several things that the LMAX team found helpful to get there. One was to write custom implementations of the Java collections that were designed to be cache-friendly and careful with garbage.

Another technique to reach that top level of performance is putting attention into performance testing. I've long noticed that people talk a lot about techniques to improve performance, but the one thing that really makes a difference is to test it

Fowler ha detto che ci sono diverse cose che sono state trovate, ma ha menzionato solo un paio.

Esistono altre architetture, librerie, tecniche o "cose" che sono utili per raggiungere tali livelli di prestazioni?

    
posta Dakotah North 29.07.2011 - 14:18
fonte

4 risposte

21

Esistono tutti i tipi di tecniche per l'elaborazione di transazioni ad alte prestazioni e quello nell'articolo di Fowler è solo uno dei tanti al margine del sanguinamento. Piuttosto che elencare una serie di tecniche che potrebbero o meno essere applicabili alla situazione di chiunque, penso che sia meglio discutere i principi di base e in che modo LMAX ne indirizzi un gran numero.

Per un sistema di elaborazione delle transazioni su larga scala, si desidera eseguire quanto segue quanto più possibile:

  1. Riduci al minimo il tempo trascorso nei livelli di archiviazione più lenti. Dal più veloce al più lento su un server moderno hai: CPU / L1 - > L2 - > L3 - > RAM - > Disco / LAN - > PALLIDO. Il salto da anche il più veloce disco magnetico moderno alla RAM più lenta è oltre 1000x per l'accesso sequenziale ; l'accesso casuale è anche peggio.

  2. Riduci al minimo o elimina il tempo trascorso in attesa . Ciò significa condividere il minore stato possibile e, se lo stato deve essere condiviso, evitare i blocchi espliciti ogni volta che è possibile.

  3. Distribuisci il carico di lavoro. Le CPU non sono diventate molto più veloci negli ultimi anni, ma hanno hanno ridotto e 8 core sono piuttosto comuni su un server. Oltre a ciò, puoi persino distribuire il lavoro su più macchine, che è l'approccio di Google; il bello di questo è che ridimensiona tutto incluso I / O.

Secondo Fowler, LMAX adotta il seguente approccio per ognuno di questi:

  1. Mantieni tutti in memoria in tutti volte. La maggior parte dei motori di database lo farà comunque, se l'intero database può stare nella memoria, ma non vuole lasciare nulla al caso, il che è comprensibile su una piattaforma di trading in tempo reale. Per riuscire a ottenere questo risultato senza aggiungere un sacco di rischi, hanno dovuto creare una serie di infrastrutture di backup e di failover leggere.

  2. Utilizzare una coda senza blocco ("disruptor") per il flusso di eventi di input. Contrasta con le tradizionali code di messaggi durevoli che sono definitivamente non bloccate liberamente, e di solito coinvolgono dolorosamente -slow transazioni distribuite .

  3. Non molto. LMAX getta questo sotto il bus sulla base del fatto che i carichi di lavoro sono interdipendenti; il risultato di uno modifica i parametri per gli altri. Questo è un avvertimento critico , che Fowler chiama esplicitamente. Fanno uso di alcuni di concorrenza per fornire funzionalità di failover, ma tutta la logica di business viene elaborata su un thread singolo .

LMAX è non l'unico approccio all'OLTP su larga scala. E sebbene sia abbastanza brillante di per sé, devi non aver bisogno di utilizzare tecniche all'avanguardia per ottenere quel livello di prestazioni.

Di tutti i principi sopra riportati, # 3 è probabilmente il più importante e il più efficace, perché, francamente, l'hardware è economico. Se è possibile partizionare correttamente il carico di lavoro attraverso una mezza dozzina di core e diverse dozzine di macchine, allora il limite del cielo è convenzionale Parallel Computing tecniche. Sareste sorpresi di quanto throughput sia possibile ottenere con nient'altro che un mucchio di code di messaggi e un distributore round robin. Ovviamente non è così efficiente come LMAX - in realtà nemmeno vicino - ma il throughput, la latenza e l'economicità sono preoccupazioni separate, e qui stiamo parlando in modo specifico del throughput.

Se hai lo stesso tipo di esigenze speciali che LMAX fa - in particolare, uno stato condiviso che corrisponde a una realtà aziendale in contrasto con una scelta frettolosa di design - allora suggerirei di provare il loro componente, perché non ho Ho visto molto altro che è adatto a quei requisiti. Ma se stiamo semplicemente parlando di alta scalabilità, ti esorto a fare più ricerche sui sistemi distribuiti, perché sono l'approccio canonico usato dalla maggior parte delle organizzazioni oggi (Hadoop e progetti correlati, ESB e architetture correlate, CQRS che Fowler ha anche cita, e così via).

Gli SSD diventeranno anche un punto di svolta; probabilmente lo sono già. Ora è possibile disporre di una memoria permanente con tempi di accesso simili alla RAM, e anche se gli SSD server-grade sono ancora orribilmente costosi, alla fine scenderanno di prezzo una volta che i tassi di adozione cresceranno. È stato studiato approfonditamente ed i risultati sono piuttosto stupefacente e migliorerà solo nel tempo, quindi l'intero concetto "mantieni tutto in memoria" è molto meno importante di un tempo. Quindi, ancora una volta, proverei a concentrarmi sulla concorrenza quando possibile.

    
risposta data 29.07.2011 - 16:20
fonte
10

Penso che la lezione più grande da imparare da questo è che devi iniziare con le basi:

  • Buoni algoritmi, strutture dati appropriate e non fare nulla di "veramente stupido"
  • Codice ben calcolato
  • Test delle prestazioni

Durante i test delle prestazioni, profili il tuo codice, trovi i colli di bottiglia e li correggi uno per uno.

Troppe persone salgono direttamente alla parte "aggiusta loro una ad una". Passano un po 'di tempo a scrivere "implementazioni personalizzate delle raccolte java", perché sanno solo che l'intero motivo per cui il loro sistema è lento è dovuto a errori di cache. Questo potrebbe essere un fattore che contribuisce, ma se si passa direttamente al tweaking del codice di basso livello in questo modo, è probabile che manchi il più grande problema di usare un ArrayList quando si dovrebbe usare una LinkedList, o che il vero motivo del tuo sistema sia è lento perché il tuo ORM è figlio pigro di un'entità e quindi effettua 400 viaggi separati nel database per ogni richiesta.

    
risposta data 29.07.2011 - 20:55
fonte
7

Non commenterà particolarmente il codice LMAX perché penso che sia ampiamente descritto, ma qui ci sono alcuni esempi di cose che ho fatto che hanno portato a miglioramenti significativi delle prestazioni misurabili.

Come sempre, queste sono tecniche che dovrebbero essere applicate una volta che sai che hai un problema e hai bisogno di migliorare le prestazioni - altrimenti probabilmente stai solo facendo un'ottimizzazione prematura.

  • Utilizza la giusta struttura dati e creane uno personalizzato, se necessario - la corretta progettazione della struttura dei dati è al livello del miglioramento che otterrai dalle micro-ottimizzazioni, quindi fallo prima. Se il tuo algoritmo dipende dalle prestazioni su molte letture di accesso casuale O (1) veloci, assicurati di disporre di una struttura dati che supporti questo! Vale la pena saltare attraverso alcuni cerchi per farlo bene, ad es. trovare un modo per rappresentare i dati in una matrice per sfruttare letture indicizzate O (1) molto veloci.
  • La CPU è più veloce dell'accesso alla memoria - puoi fare un bel po 'di calcoli nel tempo necessario per far leggere una memoria casuale se la memoria non è nella cache L1 / L2. Di solito vale la pena fare un calcolo se ti salva una lettura in memoria.
  • Aiuta il compilatore JIT con il finale - rendendo i campi, i metodi e le classi finali consente ottimizzazioni specifiche che aiutano veramente il compilatore JIT. Esempi specifici:

    • Il compilatore può assumere che una classe finale non abbia sottoclassi, quindi può trasformare le chiamate di metodo virtuali in chiamate di metodo statiche
    • Il compilatore può trattare i campi finali statici come una costante per un buon miglioramento delle prestazioni, specialmente se la costante viene quindi utilizzata in calcoli che possono essere calcolati in fase di compilazione.
    • Se un campo contenente un oggetto Java viene inizializzato come finale, l'ottimizzatore può eliminare sia il controllo nullo che l'invio del metodo virtuale. Nizza.
  • Sostituisci le classi di raccolta con gli array - questo si traduce in un codice meno leggibile ed è più difficile da mantenere, ma è quasi sempre più veloce in quanto rimuove uno strato di riferimento indiretto e beneficia di un gran numero di array- ottimizzazioni di accesso. Di solito è una buona idea nei loop interni / codice sensibile alle prestazioni dopo averlo identificato come un collo di bottiglia, ma evitare altrimenti per motivi di leggibilità!

  • Utilizza le primitive ovunque sia possibile - le primitive sono fondamentalmente più veloci dei loro equivalenti basati su oggetti. In particolare, il pugilato aggiunge una quantità enorme di overhead e può causare brutte pause GC. Non permettere che le primitive vengano inscatolate se ti preoccupi delle prestazioni / della latenza.

  • Riduci a icona il blocco a basso livello - i blocchi sono molto costosi a un livello basso. Trova i modi per evitare il blocco totale o il blocco a un livello a grana grossa in modo che sia sufficiente bloccare raramente su grandi blocchi di dati e il codice di basso livello possa procedere senza doversi preoccupare del blocco o dei problemi di concorrenza.

  • Evita di allocare memoria - questo potrebbe rallentare in generale, dal momento che la garbage collection JVM è incredibilmente efficiente, ma è molto utile se stai cercando di ottenere una latenza estremamente bassa e devi ridurre al minimo le pause GC. Esistono strutture dati speciali che è possibile utilizzare per evitare allocazioni: la libreria link in particolare è eccellente e notevole per queste.
risposta data 13.08.2011 - 15:52
fonte
4

Oltre a quanto già affermato in una risposta eccellente da Aaronaught Vorrei notare che un codice del genere potrebbe essere piuttosto difficile da sviluppare, capire e fare il debug. "Mentre è molto efficiente ... è molto facile rovinare ..." come uno dei loro ragazzi menzionato in blog LMAX .

  • Per uno sviluppatore abituato alle tradizionali query-e-serrature , la codifica per un nuovo approccio potrebbe sembrare di cavalcare il cavallo selvaggio. Almeno questa è stata la mia esperienza quando ho sperimentato con Phaser che il concetto è menzionato nella carta tecnica LMAX. In questo senso, direi che questo approccio scambia la contesa del blocco per la contesa cerebrale dello sviluppatore .

Considerato sopra, penso che coloro che scelgono Disruptor e approcci simili facciano meglio a garantire risorse di sviluppo sufficienti a mantenere la loro soluzione.

Nel complesso, l'approccio di Disruptor mi sembra abbastanza promettente. Anche se la tua azienda non può permettersi di utilizzarla, ad esempio per le ragioni sopra menzionate, valuta la possibilità di convincere il management a "investire" qualche sforzo nello studio (e SEDA in generale) - perché se così non fosse, c'è la possibilità che un giorno i loro clienti li lasceranno a favore di qualche soluzione più competitiva che richiede server 4x, 8x ecc.

    
risposta data 13.08.2011 - 15:08
fonte

Leggi altre domande sui tag