Ondanks de vele voordelen wordt Ruby on Rails nog steeds beschouwd als een relatief traag webraamwerk. We weten allemaal dat Twitter Rails heeft verlaten ten gunste van Scala. Met een paar slimme verbeteringen kun je je app echter aanzienlijk sneller laten draaien!
Eerst Ruby
Ruby is een sterk objectgeoriënteerde taal. In feite is (bijna) alles in Ruby een object is. Het maken van onnodige objecten kan je programma veel extra geheugengebruik kosten, dus je moet dit vermijden.
Om het verschil te meten, gebruiken we een geheugen_profiler gem en een ingebouwde Benchmark-module om de tijdprestaties te meten.
Gebruik bang! methoden op strings
vereist "geheugen_profiler".
rapport = MemoryProfiler.report doen
data = "X" * 1024 * 1024 * 100
data = data.downcase
einde
rapport.pretty_print
In de onderstaande lijst hebben we een string van 100 MB gemaakt en elk karakter daarin gedowncased. Onze benchmark geeft ons het volgende rapport:
Totaal toegewezen: 210765044 bytes (6 objecten)
Als we regel 6 echter vervangen door:
data.downcase!
Bestanden regel voor regel lezen
Vermoedelijk moeten we een enorme gegevensverzameling van 2 miljoen records ophalen uit een csv-bestand. Typisch zou het er zo uitzien:
vereisen 'benchmark'
Benchmark.bm do |x|
x.report do
File.readlines("2mrecords.csv").map! {|line| line.split(",")}
end
einde
Het kostte ons meer dan 106 seconden om het bestand volledig te downloaden. Behoorlijk veel! Maar we kunnen dit proces versnellen door de kaart! methode met een eenvoudige terwijl lus:
vereisen 'benchmark'
Benchmark.bm do |x|
x.report do
bestand = bestand.open("2mrecords.csv", "r")
while regel = bestand.gets
regel.split(",")
einde
einde
einde
De looptijd is nu drastisch gedaald sinds de kaart! methode behoort tot een specifieke klasse, zoals Hash#map of Array#mapwaarbij Ruby zal elke regel van het geparseerde bestand in het geheugen opslaan zolang het wordt uitgevoerd. Ruby's vuilnisman zal het geheugen niet vrijgeven voordat die iterators volledig zijn uitgevoerd. Door het echter regel voor regel te lezen, zal GC het geheugen van de vorige regels verplaatsen als dat niet nodig is.
Vermijd methode iterators op grotere verzamelingen
Dit is een uitbreiding van het vorige punt met een meer algemeen voorbeeld. Zoals ik al zei, Ruby iterators zijn objectmethoden en geven het geheugen niet vrij zolang ze worden uitgevoerd. Op kleine schaal is het verschil zinloos (en methoden zoals kaart leesbaarder lijkt). Als het echter om grotere gegevenssets gaat, is het altijd een goed idee om te overwegen deze te vervangen door meer eenvoudige lussen. Zoals in het onderstaande voorbeeld:
aantalelementen = 10000000
randoms = Array.new(numberofelements) { rand(10) }
randoms.each do
#doe iets
einde
en na het refactoren:
aantalelementen = 10000000
randoms = Array.new(numberofelements) { rand(10) }
terwijl randoms.count > 0
regel = randoms.shift
#doe iets
einde
"`
Gebruik de methode String::<<
Dit is een snelle maar bijzonder nuttige tip. Als je een string toevoegt aan een andere met de += operator achter de schermen. Ruby zal een extra object maken. Dus dit:
a = "X"
b = "Y"
a += b
Betekent eigenlijk dit:
a = "X"
b = "Y"
c = a + b
a = c
Operator zou dat vermijden, waardoor je wat geheugen bespaart:
a = "X"
b = "Y"
a << b
Laten we het over Rails hebben
De Rails raamwerk bezit veel "gotchas" waarmee je je code snel en zonder al te veel extra moeite.
Eager Loading AKA n+1 query probleem
Laten we aannemen dat we twee geassocieerde modellen hebben, Post en Auteur:
klasse Auteur < Toepassingsregistratie
heeft_veel :berichten
einde
klasse Post < Toepassingsrecord
hoort bij :auteur
einde
We willen alle berichten ophalen in onze controller en ze weergeven in een weergave met hun auteurs:
controller
def index
@posts = Post.all.limit(20)
einde
bekijken
In de controller, ActiveRecord zal slechts één query aanmaken om onze berichten te vinden. Maar later zal het ook nog eens 20 query's genereren om elke auteur te vinden - wat extra tijd kost! Gelukkig heeft Rails een snelle oplossing om die query's te combineren tot één query. Door de omvat methode kunnen we onze controller op deze manier herschrijven:
def index
@posts = Post.all.includes(:author).limit(20)
einde
Voorlopig worden alleen de benodigde gegevens opgehaald in één query.
Je kunt ook andere edelstenen gebruiken, zoals kogel om het hele proces aan te passen.
Bel alleen wat je nodig hebt
Een andere handige techniek om de snelheid van ActiveRecord te verhogen is door alleen die attributen aan te roepen die nodig zijn voor je huidige doeleinden. Dit is vooral handig als je app begint te groeien en het aantal kolommen per tabel ook toeneemt.
Laten we onze vorige code als voorbeeld nemen en aannemen dat we alleen namen uit auteurs hoeven te selecteren. Dus kunnen we onze controller herschrijven:
def index
@posts = Post.all.includes(:author).select("naam").limit(20)
einde
Nu instrueren we onze controller om alle attributen over te slaan, behalve degene die we nodig hebben.
Partialen goed weergeven
Laten we zeggen dat we een aparte partitie willen maken voor onze berichten uit eerdere voorbeelden:
Op het eerste gezicht ziet deze code er correct uit. Als er echter een groter aantal berichten moet worden gerenderd, zal het hele proces aanzienlijk langzamer verlopen. Dit komt doordat Rails roept onze partial opnieuw op met een nieuwe iteratie. We kunnen dit oplossen door de collecties functie:
Nu, Rails zal automatisch uitzoeken welke sjabloon gebruikt moet worden en deze slechts eenmaal initialiseren.
Achtergrondverwerking gebruiken
Elk proces dat meer tijd in beslag neemt en niet cruciaal is voor je huidige stroom, kan worden beschouwd als een goede kandidaat voor achtergrondverwerking, bijvoorbeeld het verzenden van e-mails, het verzamelen van statistieken of het leveren van periodieke rapporten.
Sidekiq is de meest gebruikte edelsteen voor achtergrondverwerking. Het gebruikt Redis om taken op te slaan. Je kunt er ook de stroom van je achtergrondprocessen mee regelen, ze opsplitsen in aparte wachtrijen en het geheugengebruik per proces beheren.
Minder code schrijven, meer edelstenen gebruiken
Rails kwam met een enorm aantal edelstenen die niet alleen je leven eenvoudiger maken en het ontwikkelproces versnellen, maar ook de prestatiesnelheid van je applicatie verhogen. Gems zoals Devise of Pundit zijn meestal goed getest op hun snelheid en werken sneller en veiliger dan code die op maat is geschreven voor hetzelfde doel.
Als je vragen hebt over het verbeteren van Rails prestatiesbereiken The Codest ingenieurs uit om je twijfels te raadplegen.