Gestión de Memoria Ruby


He estado usando Ruby desde hace un tiempo y encuentro que, para proyectos más grandes, puede ocupar una buena cantidad de memoria. ¿Cuáles son algunas de las mejores prácticas para reducir el uso de memoria en Ruby?

  • Por favor, que cada respuesta tenga una "mejor práctica" y que la comunidad la vote.
Author: Substantial, 2008-10-08

5 answers

No hagas esto:

def method(x)
  x.split( doesn't matter what the args are )
end

O esto:

def method(x)
  x.gsub( doesn't matter what the args are )
end

Ambos perderán memoria permanentemente en ruby 1.8.5 y 1.8.6 . (no estoy seguro sobre 1.8.7 ya que no lo he probado, pero realmente espero que esté arreglado.) La solución es estúpida e implica la creación de una variable local. No tienes que usar el local, solo crea uno...

Cosas como esta son la razón por la que tengo mucho amor por el lenguaje ruby, pero no respeto por MRI

 14
Author: Orion Edwards,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2014-03-03 21:24:37

Cuando se trabaja con grandes matrices de objetos ActiveRecord tenga mucho cuidado... Al procesar esos objetos en un bucle si en cada iteración está cargando sus objetos relacionados usando has_many, belongs_to, etc. de ActiveRecord. - el uso de memoria crece mucho porque cada objeto que pertenece a un array crece...

La siguiente técnica nos ayudó mucho ( ejemplo simplificado):

students.each do |student|
  cloned_student = student.clone
  ...
  cloned_student.books.detect {...}
  ca_teachers = cloned_student.teachers.detect {|teacher| teacher.address.state == 'CA'}
  ca_teachers.blah_blah
  ...
  # Not sure if the following is necessary, but we have it just in case...
  cloned_student = nil
end

En el código anterior "cloned_student" es el objeto que crece, pero ya es "anulado" al final de cada iteración esto no es un problema para una gran variedad de estudiantes. Si no hubiéramos hecho "clone", la variable de bucle "student" habría crecido, pero como pertenece a un array, la memoria utilizada nunca se libera mientras exista el objeto array.

También funciona un enfoque diferente:

students.each do |student|
  loop_student = Student.find(student.id) # just re-find the record into local variable.
  ...
  loop_student.books.detect {...}
  ca_teachers = loop_student.teachers.detect {|teacher| teacher.address.state == 'CA'}
  ca_teachers.blah_blah
  ...
end

En nuestro entorno de producción teníamos un proceso en segundo plano que no pudo terminar una vez porque 8 Gb de RAM no era suficiente para ello. Después de este pequeño cambio utiliza menos de 1Gb para procesar la misma cantidad de datos...

 26
Author: Alex Kovshovik,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2010-09-21 22:32:05

No abuses de los símbolos.

Cada vez que creas un símbolo, ruby pone una entrada en su tabla de símbolos. La tabla de símbolos es un hash global que nunca se vacía.
Esto no es técnicamente una fuga de memoria, pero se comporta como una. Los símbolos no ocupan mucha memoria por lo que no es necesario ser demasiado paranoico, pero vale la pena ser consciente de esto.

Una guía general: Si realmente has escrito el símbolo en código, está bien (después de todo, solo tienes una cantidad finita de código), pero no llame a to_sym en cadenas generadas dinámicamente o de entrada de usuario, ya que esto abre la puerta a un número potencialmente cada vez mayor

 21
Author: Orion Edwards,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2008-10-08 05:12:22

Tenga cuidado con las extensiones C que asignan grandes porciones de memoria.

Por ejemplo, cuando carga una imagen usando RMagick, el mapa de bits completo se carga en la memoria dentro del proceso ruby. Esto puede ser de unos 30 meg dependiendo del tamaño de la imagen.
Sin embargo , la mayor parte de esta memoria ha sido asignada por el propio RMagick. Todo lo que ruby conoce es un objeto wrapper, que es tiny(1).
Ruby solo piensa que se está aferrando a una pequeña cantidad de memoria, así que no se molestará dirigiendo el GC. De hecho, tiene 30 meg.
Si haces un bucle sobre 10 imágenes, puedes quedarte sin memoria muy rápido.

La solución preferida es decirle manualmente a la biblioteca C que limpie la memoria en sí - RMagick tiene un destroy! método que hace esto. Sin embargo, si su biblioteca no lo hace, es posible que deba ejecutar por la fuerza el GC usted mismo, aunque esto generalmente se desaconseja.

(1): Las extensiones Ruby C tienen callbacks que se ejecutarán cuando el ruby runtime decide liberarlos, por lo que la memoria eventualmente se liberará con éxito en algún momento, quizás no lo suficientemente pronto.

 9
Author: Orion Edwards,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2008-10-08 05:09:20

Mida y detecte qué partes de su código están creando objetos que hacen que el uso de memoria aumente. Mejore y modifique su código y luego mida de nuevo. A veces, estás usando gemas o bibliotecas que consumen mucha memoria y también crean muchos objetos.

Hay muchas herramientas por ahí, como busy-administrator que le permiten comprobar el tamaño de memoria de los objetos (incluidos los que están dentro de hashes y matrices).

$ gem install busy-administrator

Ejemplo # 1: MemorySize.de

require 'busy-administrator'

data = BusyAdministrator::ExampleGenerator.generate_string_with_specified_memory_size(10.mebibytes)

puts BusyAdministrator::MemorySize.of(data)
# => 10 MiB

Ejemplo # 2: MemoryUtils.perfil

Código

require 'busy-administrator'

results = BusyAdministrator::MemoryUtils.profile(gc_enabled: false) do |analyzer|
  BusyAdministrator::ExampleGenerator.generate_string_with_specified_memory_size(10.mebibytes)
end  

BusyAdministrator::Display.debug(results)

Salida:

{
    memory_usage:
        {
            before: 12 MiB
            after: 22 MiB
            diff: 10 MiB
        }
    total_time: 0.406452
    gc:
        {
            count: 0
            enabled: false
        }
    specific:
        {
        }
    object_count: 151
    general:
        {
            String: 10 MiB
            Hash: 8 KiB
            BusyAdministrator::MemorySize: 0 Bytes
            Process::Status: 0 Bytes
            IO: 432 Bytes
            Array: 326 KiB
            Proc: 72 Bytes
            RubyVM::Env: 96 Bytes
            Time: 176 Bytes
            Enumerator: 80 Bytes
        }
}

También puede intentar ruby-prof y memory_profiler. Es mejor si prueba y experimenta diferentes versiones de su código para que pueda medir el uso de memoria y el rendimiento de cada versión. Esto le permitirá comprobar si su optimización realmente funcionó o no. Por lo general, utiliza estas herramientas en el modo de desarrollo / prueba y las desactiva en producción.

 0
Author: Joshua Arvin Lat,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2016-09-25 15:46:25