trata repentina que por perdida para memoria medicamentos jovenes estres especialista enfermedad ancianos ruby memory-leaks valgrind

repentina - Encontrar la causa de una pérdida de memoria en Ruby



perdida de memoria temporal (4)

Descubrí una pérdida de memoria en mi código de Rails, es decir, encontré qué código pierde, pero no por qué se filtra. Lo he reducido a un testcase que no requiere Rails:

require ''csspool'' require ''ruby-mass'' def report puts ''Memory '' + `ps ax -o pid,rss | grep -E "^[[:space:]]*#{$$}"`.strip.split.map(&:to_i)[1].to_s + ''KB'' Mass.print end report # note I do not store the return value here CSSPool::CSS::Document.parse(File.new(''/home/jason/big.css'')) ObjectSpace.garbage_collect sleep 1 report

ruby-mass supuestamente me permite ver todos los objetos en la memoria. CSSPool es un analizador de CSS basado en racc . /home/jason/big.css es un archivo CSS de 1.5MB .

Esto produce:

Memory 9264KB ================================================== Objects within [] namespace ================================================== String: 7261 RubyVM::InstructionSequence: 1151 Array: 562 Class: 313 Regexp: 181 Proc: 111 Encoding: 99 Gem::StubSpecification: 66 Gem::StubSpecification::StubLine: 60 Gem::Version: 60 Module: 31 Hash: 29 Gem::Requirement: 25 RubyVM::Env: 11 Gem::Specification: 8 Float: 7 Gem::Dependency: 7 Range: 4 Bignum: 3 IO: 3 Mutex: 3 Time: 3 Object: 2 ARGF.class: 1 Binding: 1 Complex: 1 Data: 1 Gem::PathSupport: 1 IOError: 1 MatchData: 1 Monitor: 1 NoMemoryError: 1 Process::Status: 1 Random: 1 RubyVM: 1 SystemStackError: 1 Thread: 1 ThreadGroup: 1 fatal: 1 ================================================== Memory 258860KB ================================================== Objects within [] namespace ================================================== String: 7456 RubyVM::InstructionSequence: 1151 Array: 564 Class: 313 Regexp: 181 Proc: 113 Encoding: 99 Gem::StubSpecification: 66 Gem::StubSpecification::StubLine: 60 Gem::Version: 60 Module: 31 Hash: 30 Gem::Requirement: 25 RubyVM::Env: 13 Gem::Specification: 8 Float: 7 Gem::Dependency: 7 Range: 4 Bignum: 3 IO: 3 Mutex: 3 Time: 3 Object: 2 ARGF.class: 1 Binding: 1 Complex: 1 Data: 1 Gem::PathSupport: 1 IOError: 1 MatchData: 1 Monitor: 1 NoMemoryError: 1 Process::Status: 1 Random: 1 RubyVM: 1 SystemStackError: 1 Thread: 1 ThreadGroup: 1 fatal: 1 ==================================================

Puedes ver la memoria subiendo. Algunos de los contadores suben, pero no hay objetos específicos de CSSPool. Usé el método de "índice" de ruby-mass para inspeccionar los objetos que tienen referencias como esta:

Mass.index.each do |k,v| v.each do |id| refs = Mass.references(Mass[id]) puts refs if !refs.empty? end end

Pero, de nuevo, esto no me da nada relacionado con CSSPool, solo información de gemas y tal.

También he intentado dar salida "GC.stat" ...

puts GC.stat CSSPool::CSS::Document.parse(File.new(''/home/jason/big.css'')) ObjectSpace.garbage_collect sleep 1 puts GC.stat

Resultado:

{:count=>4, :heap_used=>126, :heap_length=>138, :heap_increment=>12, :heap_live_num=>50924, :heap_free_num=>24595, :heap_final_num=>0, :total_allocated_object=>86030, :total_freed_object=>35106} {:count=>16, :heap_used=>6039, :heap_length=>12933, :heap_increment=>3841, :heap_live_num=>13369, :heap_free_num=>2443302, :heap_final_num=>0, :total_allocated_object=>3771675, :total_freed_object=>3758306}

Como lo entiendo, si un objeto no está referenciado y ocurre una recolección de basura, entonces ese objeto debería borrarse de la memoria. Pero eso no parece ser lo que está pasando aquí.

También he leído sobre las fugas de memoria de nivel C, y dado que CSSPool usa Racc que usa el código C, creo que esta es una posibilidad. He ejecutado mi código a través de Valgrind:

valgrind --partial-loads-ok=yes --undef-value-errors=no --leak-check=full --fullpath-after= ruby leak.rb 2> valgrind.txt

Los resultados están here . No estoy seguro de si esto confirma una fuga de nivel C, ya que también leí que Ruby hace cosas con memoria que Valgrind no entiende.

Versiones utilizadas:

  • Ruby 2.0.0-p247 (esto es lo que ejecuta mi aplicación Rails)
  • Ruby 1.9.3-p392-ref (para pruebas con masa de rubí)
  • Ruby-Mass 0.1.3
  • CSSPool 4.0.0 desde here
  • CentOS 6.4 y Ubuntu 13.10

Está bien, he encontrado la respuesta. Dejo mi otra respuesta porque la información era muy difícil de recopilar, está relacionada y podría ayudar a otra persona a buscar un problema relacionado.

Su problema, sin embargo, parece deberse al hecho de que Ruby en realidad no libera la memoria al sistema operativo una vez que la ha adquirido.

Asignación de memoria

Si bien los programadores de Ruby no suelen preocuparse por la asignación de memoria, a veces surge la siguiente pregunta:

¿Por qué mi proceso de Ruby se mantuvo tan grande incluso después de haber borrado todas las referencias a objetos grandes? Estoy / seguro / GC ha corrido varias veces y liberado mis objetos grandes y no estoy perdiendo memoria.

El programador de CA podría hacer la misma pregunta:

He liberado () mucha memoria, ¿por qué mi proceso sigue siendo tan grande?

La asignación de memoria al espacio del usuario desde el kernel es más barata en grandes porciones, por lo que el espacio del usuario evita la interacción con el kernel haciendo más trabajo por sí mismo.

Las bibliotecas de espacio de usuario / tiempos de ejecución implementan un asignador de memoria (por ejemplo: malloc (3) en libc) que toma grandes porciones de memoria del núcleo2 y las divide en partes más pequeñas para que las utilicen las aplicaciones de espacio de usuario.

Por lo tanto, pueden ocurrir varias asignaciones de memoria de espacio de usuario antes de que el espacio de usuario necesite pedir más memoria al núcleo. Por lo tanto, si obtuviste una gran parte de la memoria del kernel y solo estás usando una pequeña parte de eso, esa gran parte de la memoria permanece asignada.

Liberar la memoria de nuevo en el kernel también tiene un costo. Los asignadores de memoria de espacio de usuario pueden conservar esa memoria (en privado) con la esperanza de que puedan reutilizarse dentro del mismo proceso y no devolverla al kernel para usarla en otros procesos. (Mejores Prácticas de Ruby)

Por lo tanto, sus objetos pueden muy bien haber sido recolectados en basura y devueltos a la memoria disponible de Ruby, pero debido a que Ruby nunca devuelve la memoria no utilizada al sistema operativo, el valor rss para el proceso sigue siendo el mismo, incluso después de la recolección de basura. Esto es en realidad por diseño. Según Mike Perham :

... Y como MRI nunca devuelve la memoria no utilizada, nuestro demonio puede tomar fácilmente 300-400MB cuando solo usa 100-200.

Es importante tener en cuenta que esto es esencialmente por diseño. El historial de Ruby es principalmente como una herramienta de línea de comandos para el procesamiento de texto y, por lo tanto, valora el inicio rápido y una pequeña huella de memoria. No fue diseñado para procesos de servidor / demonio de larga ejecución. Java hace una compensación similar en sus máquinas virtuales de cliente y servidor.


Esto podría deberse a la función "Barrido lento" en Ruby 1.9.3 y superior.

El barrido lento básicamente significa que, durante la recolección de basura, Ruby solo "barre" los objetos suficientes para crear espacio para los nuevos objetos que necesita crear. Lo hace porque, mientras que el recolector de basura Ruby se ejecuta, nada más lo hace. Esto se conoce como recolección de basura "Detener el mundo".

Esencialmente, el barrido lento reduce el tiempo que Ruby necesita para "detener el mundo". Puedes leer más sobre barrido perezoso here .

¿Cómo se RUBY_GC_MALLOC_LIMIT tu variable de entorno RUBY_GC_MALLOC_LIMIT ?

Aquí hay un extracto del blog de Sam Saffron sobre barrido lento y el RUBY_GC_MALLOC_LIMIT:

El GC en Ruby 2.0 viene en 2 sabores diferentes. Tenemos un GC "completo" que se ejecuta después de que asignamos más que nuestro malloc_limit y un barrido perezoso (GC parcial) que se ejecutará si alguna vez nos quedamos sin ranuras libres en nuestros montones.

El barrido lento toma menos tiempo que un GC completo, sin embargo, solo realiza un GC parcial. Su objetivo es realizar un GC corto con mayor frecuencia, lo que aumenta el rendimiento general. El mundo se detiene, pero por menos tiempo.

El malloc_limit se establece en 8 MB fuera de la caja, puede aumentarlo configurando el RUBY_GC_MALLOC_LIMIT más alto.

¿Es su RUBY_GC_MALLOC_LIMIT extremadamente alto? El mío se establece en 100000000 (100MB). El valor predeterminado es de alrededor de 8 MB, pero para las aplicaciones de rieles, recomiendan que sea un poco más alto. Si el tuyo es demasiado alto, podría estar impidiendo que Ruby elimine objetos de basura, porque cree que tiene mucho espacio para crecer.


Parece que estás entrando en el mundo perdido aquí. No creo que el problema sea con c-bindings en racc tampoco.

La gestión de la memoria de Ruby es elegante y engorroso. Almacena objetos (llamados RVALUE s) en los llamados montones de tamaño de aproximadamente 16KB. En un nivel bajo, RVALUE es una estructura en C, que contiene una union de diferentes representaciones de objetos ruby ​​estándar.

Por lo tanto, los montones almacenan objetos RVALUE , cuyo tamaño no supera los 40 bytes. Para objetos como String , Array , Hash , etc., esto significa que los objetos pequeños pueden caber en el montón, pero tan pronto como alcancen un umbral, se asignará una memoria adicional fuera de los montones de Ruby.

Esta memoria extra es flexible; Se liberará tan pronto como un objeto se convierta en GC''ed. Es por eso que tu testcase con big_string muestra el comportamiento de memoria arriba-abajo:

def report puts ''Memory '' + `ps ax -o pid,rss | grep -E "^[[:space:]]*#{$$}"` .strip.split.map(&:to_i)[1].to_s + ''KB'' end report big_var = " " * 10000000 report big_var = nil report ObjectSpace.garbage_collect sleep 1 report # ⇒ Memory 11788KB # ⇒ Memory 65188KB # ⇒ Memory 65188KB # ⇒ Memory 11788KB

Pero los montones (ver GC[:heap_length] ) en sí mismos no se devuelven al sistema operativo, una vez adquiridos. Mira, haré un cambio monótono en tu testcase:

- big_var = " " * 10000000 + big_var = 1_000_000.times.map(&:to_s)

Y voilá:

# ⇒ Memory 11788KB # ⇒ Memory 65188KB # ⇒ Memory 65188KB # ⇒ Memory 57448KB

La memoria ya no se libera al sistema operativo, porque cada elemento de la matriz que introduje se adapta al tamaño de RVALUE y se almacena en el montón de rubíes.

Si examina la salida de GC.stat después de GC.stat el GC, encontrará que el valor de GC[:heap_used] disminuye como se esperaba. Ruby ahora tiene un montón de montones vacíos, listo.

El resumen: no creo, el código c gotea. Creo que el problema está dentro de la representación base64 de una imagen enorme en tu css . No tengo ni idea de lo que sucede dentro del analizador, pero parece que la enorme cuerda hace que aumente la cantidad de rubíes.

Espero eso ayude.


Sobre la base de la explicación de @mudasobwa, finalmente localicé la causa. El código en CSSPool estaba revisando la URI de datos muy larga en busca de secuencias de escape. scan a scan en el URI con una expresión regular que coincidía con una secuencia de escape o un solo carácter, map esos resultados a unescape y luego los join nuevo en una cadena. Esto estaba efectivamente asignando una cadena para cada carácter en el URI. Lo modifiqué para gsub las secuencias de escape, que parecen tener los mismos resultados (todas las pruebas pasan) y reduce en gran medida la memoria final utilizada.

Usando el mismo caso de prueba que se publicó originalmente (menos la salida de Mass.print ), este es el resultado antes del cambio:

Memory 12404KB Memory 292516KB

Y este es el resultado después del cambio:

Memory 12236KB Memory 19584KB