Nonostante i suoi numerosi vantaggi, Ruby on Rails è ancora considerato un framework web relativamente lento. Sappiamo tutti che Twitter ha abbandonato Rails a favore di Scala. Tuttavia, con alcuni miglioramenti intelligenti è possibile eseguire la propria applicazione in modo significativamente più veloce!
Prima il rubino
Rubino è un linguaggio fortemente orientato agli oggetti. Infatti, (quasi) tutto in Rubino è un oggetto. La creazione di oggetti non necessari potrebbe costare al programma un notevole utilizzo di memoria aggiuntiva, quindi è necessario evitarla.
Per misurare la differenza, utilizzeremo un profilatore_di_memoria e un modulo Benchmark integrato per misurare le prestazioni del tempo.
Utilizzare i metodi bang! sulle stringhe
richiedere "memory_profiler"
report = MemoryProfiler.report do
dati = "X" * 1024 * 1024 * 100
dati = dati.downcase
fine
report.pretty_print
Nell'elenco che segue, abbiamo creato una stringa di 100 MB e abbiamo ridotto ogni carattere in essa contenuto. Il nostro benchmark ci fornisce il seguente rapporto:
Totale allocato: 210765044 byte (6 oggetti)
Tuttavia, se sostituiamo la riga 6 con:
data.downcase!
Leggere i file riga per riga
Si suppone che si debba recuperare un'enorme raccolta di dati di 2 milioni di record da un file csv. In genere, il file si presenta in questo modo:
richiedere 'benchmark'
Benchmark.bm do |x|
x.report do
File.readlines("2mrecords.csv").map! {|linea|linea.split(",")}
fine
fine
utente sistema totale reale
12.797000 2.437000 15.234000 (106.319865)
Abbiamo impiegato più di 106 secondi per scaricare completamente il file. Un bel po'! Ma possiamo accelerare questo processo sostituendo il parametro mappa! con un semplice metodo mentre ciclo:
richiedere 'benchmark'
Benchmark.bm do |x|
x.report do
file = File.open("2mrecords.csv", "r")
while line = file.gets
line.split(",")
fine
fine
fine
utente sistema totale reale
6.078000 0.250000 6.328000 ( 6.649422)
Il tempo di esecuzione si è ridotto drasticamente da quando il mappa! appartiene a una classe specifica, come Hash#map o Array#map, dove Rubino memorizzerà ogni riga del file analizzato all'interno della memoria per tutto il tempo in cui viene eseguito. Il garbage collector di Ruby non rilascerà la memoria prima che gli iteratori siano stati eseguiti completamente. Tuttavia, leggendo riga per riga, GC riposizionerà la memoria dalle righe precedenti quando non è necessario.
Evitare gli iteratori dei metodi su raccolte più grandi
Questa è un'estensione del punto precedente con un esempio più comune. Come ho già detto, Rubino Gli iteratori sono metodi di oggetti e non rilasciano la memoria finché vengono eseguiti. Su piccola scala, la differenza non è significativa (e metodi come mappa sembra più leggibile). Tuttavia, quando si tratta di insiemi di dati più grandi, è sempre una buona idea considerare la possibilità di sostituirli con cicli più semplici. Come nell'esempio seguente:
numberofelements = 10000000
randoms = Array.new(numberofelements) { rand(10) }
randoms.each do |linea|
#fare qualcosa
fine
e dopo la rifattorizzazione:
numberofelements = 10000000
randoms = Array.new(numberofelements) { rand(10) }
mentre randoms.count > 0
linea = randoms.shift
#fare qualcosa
fine
"`
Utilizzare il metodo String::<<
Si tratta di un suggerimento rapido ma particolarmente utile. Se si aggiunge una stringa a un'altra usando l'operatore += dietro le quinte. Rubino creerà un oggetto aggiuntivo. Quindi, questo:
a = "X"
b = "Y"
a += b
In realtà significa questo:
a = "X"
b = "Y"
c = a + b
a = c
L'operatore lo eviterebbe, risparmiando un po' di memoria:
a = "X"
b = "Y"
a << b
Parliamo di Rails
Il Struttura Rails possiede un'abbondanza di "gotchas" che vi permetterà di ottimizzare il vostro codice rapidamente e senza troppi sforzi aggiuntivi.
Carico ansioso alias problema delle n+1 query
Supponiamo di avere due modelli associati, Post e Author:
classe Autore < ApplicationRecord
has_many :post
fine
classe Post < ApplicationRecord
appartiene a :author
fine
Vogliamo recuperare tutti i post nel nostro controllore e renderli in una vista con i loro autori:
controllore
def indice
@posts = Post.all.limit(20)
fine
vista
Nel controllore, ActiveRecord creerà una sola query per trovare i nostri post. In seguito, però, verranno attivate altre 20 query per trovare ciascun autore, con un ulteriore dispendio di tempo! Fortunatamente, Rails offre una soluzione rapida per combinare queste query in una sola. Utilizzando l'opzione comprende possiamo riscrivere il nostro controllore in questo modo:
def index
@posts = Post.all.includes(:author).limit(20)
fine
Per il momento, solo i dati necessari verranno recuperati in un'unica query.
È possibile utilizzare anche altre gemme, come ad esempio proiettile per personalizzare l'intero processo.
Chiamate solo ciò che vi serve
Un'altra tecnica utile per aumentare la velocità di ActiveRecord consiste nel richiamare solo gli attributi necessari per gli scopi attuali. Questo è particolarmente utile quando l'applicazione inizia a crescere e aumenta anche il numero di colonne per tabella.
Prendiamo come esempio il codice precedente e supponiamo di dover selezionare solo i nomi dagli autori. Quindi, possiamo riscrivere il nostro controllore:
def index
@posts = Post.all.includes(:author).select("name").limit(20)
fine
Ora istruiamo il nostro controllore a saltare tutti gli attributi, tranne quello di cui abbiamo bisogno.
Rendering corretto delle parziali
Supponiamo di voler creare un partial separato per i post degli esempi precedenti:
A prima vista, questo codice sembra corretto. Tuttavia, con un numero maggiore di post da rendere, l'intero processo sarà significativamente più lento. Questo perché Rotaie richiama il nostro partial con una nuova iterazione. Possiamo risolvere il problema utilizzando l'opzione collezioni caratteristica:
Ora, Rotaie capirà automaticamente quale modello deve essere usato e lo inizializzerà una sola volta.
Utilizzare l'elaborazione in background
Tutti i processi che richiedono più tempo e non sono cruciali per il vostro flusso attuale possono essere considerati un buon candidato per l'elaborazione in background, ad esempio l'invio di e-mail, la raccolta di statistiche o la fornitura di report periodici.
Sidekiq è la gemma più comunemente usata per l'elaborazione in background. Utilizza Redis per memorizzare le attività. Permette inoltre di controllare il flusso dei processi in background, di dividerli in code separate e di gestire l'uso della memoria per ognuno di essi.
Scrivere meno codice, usare più gemme
Rotaie ha messo a disposizione un numero enorme di gemme che non solo rendono la vita più facile e accelerano il processo di sviluppo, ma aumentano anche la velocità delle prestazioni dell'applicazione. Gemme come Devise o Pundit sono solitamente ben testate per quanto riguarda la loro velocità e funzionano in modo più rapido e sicuro rispetto al codice scritto su misura per lo stesso scopo.
In caso di domande per migliorare Prestazioni di Rails, raggiungere Ingegneri The Codest per consultare i vostri dubbi.