A pesar de sus numerosas ventajas, Ruby on Rails sigue considerándose un framework web relativamente lento. Todos sabemos que Twitter ha abandonado Rails en favor de Scala. Sin embargo, con unas cuantas mejoras inteligentes puedes hacer que tu aplicación funcione mucho más rápido.
Rubí Primero
Ruby es un lenguaje fuertemente orientado a objetos. De hecho, (casi) todo en Ruby es un objeto. La creación de objetos innecesarios puede costar a su programa una gran cantidad de uso de memoria adicional, por lo que debe evitarlo.
Para medir la diferencia, utilizaremos un perfil_de_memoria y un módulo Benchmark integrado para medir el rendimiento temporal.
Utilizar métodos bang! en cadenas
require "perfil_de_memoria"
report = MemoryProfiler.report do
datos = "X" * 1024 * 1024 * 100
data = data.downcase
end
informe.pretty_print
En el siguiente listado, hemos creado una cadena de 100 MB y hemos reducido cada uno de los caracteres que contiene. Nuestro benchmark nos da el siguiente informe:
Total asignado: 210765044 bytes (6 objetos)
Sin embargo, si sustituimos la línea 6 por:
¡data.downcase!
Leer archivos línea por línea
Supuestamente, necesitamos recuperar una enorme colección de datos de 2 millones de registros de un archivo csv. Típicamente, se vería así:
exigir "benchmark
Benchmark.bm do |x|
x.report do
¡Archivo.readlines("2mrecords.csv").map! {|line| line.split(",")}
end
end
usuario sistema total real
12.797000 2.437000 15.234000 (106.319865)
Tardamos más de 106 segundos en descargar completamente el archivo. Bastante. Pero podemos acelerar este proceso sustituyendo la directiva ¡Mapa! con un simple mientras que bucle:
exigir "benchmark
Benchmark.bm do |x|
x.informe do
archivo = File.open("2mrecords.csv", "r")
while línea = archivo.obtiene
line.split(",")
end
end
fin
usuario sistema total real
6.078000 0.250000 6.328000 ( 6.649422)
El tiempo de ejecución se ha reducido drásticamente desde el ¡Mapa! pertenece a una clase específica, como Hash1TP63Mapa o Matriz1TP63Mapadonde Ruby almacenará cada línea del archivo analizado en la memoria mientras se ejecute. El recolector de basura de Ruby no liberará la memoria antes de que esos iteradores se ejecuten completamente. Sin embargo, si se lee línea por línea, GC reubicará la memoria de las líneas anteriores cuando no sea necesario.
Evitar iteradores de métodos en colecciones grandes
Éste es una ampliación del punto anterior con un ejemplo más común. Como ya he dicho, Ruby Los iteradores son métodos de objetos y no liberarán memoria mientras se estén ejecutando. A pequeña escala, la diferencia no tiene sentido (y métodos como mapa parece más legible). Sin embargo, cuando se trata de conjuntos de datos más grandes, siempre es una buena idea considerar su sustitución por bucles más básicos. Como en el ejemplo siguiente:
númerodeelementos = 10000000
randoms = Array.new(númerodeelementos) { rand(10) }
randoms.each do |line|
#hacer algo
end
y después de la refactorización:
númerodeelementos = 10000000
randoms = Array.new(númerodeelementos) { rand(10) }
while randoms.count > 0
línea = randoms.shift
1TP63Hacer algo
fin
"`
Utilizar el método String::<<
Este es un consejo rápido pero especialmente útil. Si añades una cadena a otra utilizando el operador += entre bastidores. Ruby creará un objeto adicional. Entonces, esto:
a = "X"
b = "Y"
a += b
En realidad significa esto:
a = "X"
b = "Y"
c = a + b
a = c
El operador lo evitaría, ahorrándote algo de memoria:
a = "X"
b = "Y"
a << b
Hablemos de Rails
En Marco Rails posee un montón de "gotchas" que le permitiría optimizar su código rápidamente y sin demasiado esfuerzo adicional.
Eager Loading AKA problema de consulta n+1
Supongamos que tenemos dos modelos asociados, Post y Author:
class Autor < ApplicationRecord
has_many :posts
end
clase Post < ApplicationRecord
belongs_to :autor
end
Queremos obtener todas las entradas en nuestro controlador y mostrarlas en una vista con sus autores:
controlador
def index
@posts = Post.all.limit(20)
fin
ver
En el controlador, ActiveRecord creará sólo una consulta para encontrar nuestras entradas. Pero más tarde, también lanzará otras 20 consultas para encontrar a cada autor - ¡lo que nos llevará un tiempo adicional! Por suerte, Rails ofrece una solución rápida para combinar esas consultas en una sola. Usando el método incluye podemos reescribir nuestro controlador de esta manera:
def index
@posts = Post.all.includes(:autor).limit(20)
end
Por ahora, sólo se obtendrán los datos necesarios en una consulta.
También puede utilizar otras gemas, como bala para personalizar todo el proceso.
Llame sólo lo que necesite
Otra técnica útil para aumentar la velocidad de ActiveRecord es llamar sólo a aquellos atributos que son necesarios para tus propósitos actuales. Esto es especialmente útil cuando tu aplicación empieza a crecer y el número de columnas por tabla también aumenta.
Tomemos nuestro código anterior como ejemplo y supongamos que sólo necesitamos seleccionar nombres de autores. Entonces, podemos reescribir nuestro controlador:
def index
@posts = Post.all.includes(:autor).select("nombre").limit(20)
end
Ahora ordenamos a nuestro controlador que omita todos los atributos excepto el que necesitamos.
Renderizar parciales correctamente
Digamos que queremos crear un parcial separado para nuestros posts de ejemplos anteriores:
A primera vista, este código parece correcto. Sin embargo, con un mayor número de entradas para renderizar, todo el proceso será significativamente más lento. Esto se debe a que Rieles vuelve a invocar nuestro parcial con una nueva iteración. Podemos solucionarlo utilizando la función colecciones característica:
>
Ahora, Rieles averiguará automáticamente qué plantilla debe utilizarse y la inicializará una sola vez.
Utilizar el proceso de fondo
Todo proceso que consuma más tiempo y no sea crucial para su flujo actual puede considerarse un buen candidato para el procesamiento en segundo plano, por ejemplo, el envío de correos electrónicos, la recopilación de estadísticas o la elaboración de informes periódicos.
Sidekiq es la gema más utilizada para el procesamiento en segundo plano. Utiliza Redis para almacenar tareas. También le permite controlar el flujo de sus procesos en segundo plano, dividirlos en colas separadas y gestionar el uso de memoria por cada uno de ellos.
Escribir menos código, utilizar más gemas
Rieles ha creado un enorme número de gemas que no sólo te facilitan la vida y aceleran el proceso de desarrollo, sino que también aumentan la velocidad de rendimiento de tu aplicación. Gemas como Devise o Pundit suelen estar bien probadas en cuanto a su velocidad y funcionan de forma más rápida y segura que el código escrito a medida para el mismo fin.
En caso de cualquier duda para mejorar Rendimiento de Rails, alcance The Codest ingenieros para consultar tus dudas.