Sé sobre el enhebrado "cooperativo" de rubí con hilos verdes . ¿Cómo puedo crear hilos reales de "nivel de sistema operativo" en mi aplicación para utilizar múltiples núcleos de CPU para el procesamiento?
Sé sobre el enhebrado "cooperativo" de rubí con hilos verdes . ¿Cómo puedo crear hilos reales de "nivel de sistema operativo" en mi aplicación para utilizar múltiples núcleos de CPU para el procesamiento?
Respuestas:
Actualizado con el comentario de Jörg de septiembre de 2011
Aquí parece confundir dos cosas muy diferentes: el lenguaje de programación Ruby y el modelo de subprocesamiento específico de una implementación específica del lenguaje de programación Ruby. Actualmente hay alrededor de 11 implementaciones diferentes del lenguaje de programación Ruby, con modelos de subprocesos muy diferentes y únicos.
(Desafortunadamente, solo dos de esas 11 implementaciones están realmente listas para su uso en producción, pero para fin de año ese número probablemente aumentará a cuatro o cinco). ( Actualización : ahora son 5: MRI, JRuby, YARV (el intérprete para Ruby 1.9), Rubinius y IronRuby).
La primera implementación en realidad no tiene un nombre, lo que hace que sea bastante incómodo referirse a ella y es realmente molesto y confuso. A menudo se le conoce como "Ruby", que es aún más molesto y confuso que no tener nombre, ya que conduce a una confusión interminable entre las características del lenguaje de programación Ruby y una implementación particular de Ruby.
A veces también se le llama "MRI" (por "Implementación de Ruby de Matz"), CRuby o MatzRuby.
MRI implementa Ruby Threads como Green Threads dentro de su intérprete . Desafortunadamente, no permite que esos hilos se programen en paralelo, solo pueden ejecutar un hilo a la vez.
Sin embargo, cualquier número de subprocesos C (subprocesos POSIX, etc.) puede ejecutarse en paralelo al subproceso Ruby, por lo que las bibliotecas C externas o las extensiones C de MRI que crean subprocesos propios aún pueden ejecutarse en paralelo.
La segunda implementación es YARV (abreviatura de "Yet Another Ruby VM"). YARV implementa Ruby Threads como POSIX o Windows NT Threads , sin embargo, utiliza un Bloqueo de intérprete global (GIL) para garantizar que solo se pueda programar un Ruby Thread a la vez.
Al igual que la resonancia magnética, los subprocesos C pueden ejecutarse paralelamente a los subprocesos Ruby.
En el futuro, es posible, que el GIL podría obtener desglosado en más bloqueos de grano fino, permitiendo así más y más código para ejecutar realmente en paralelo, pero eso es tan lejos, ni siquiera está previsto todavía.
JRuby implementa hilos Ruby como hilos nativos , donde "hilos nativos" en el caso de la JVM obviamente significa "hilos JVM". JRuby no les impone ningún bloqueo adicional. Entonces, si esos subprocesos realmente pueden ejecutarse en paralelo depende de la JVM: algunas JVM implementan subprocesos JVM como subprocesos del sistema operativo y otros como subprocesos verdes. (Las JVM principales de Sun / Oracle utilizan exclusivamente subprocesos del sistema operativo desde JDK 1.3)
XRuby también implementa Ruby Threads como JVM Threads . Actualización : XRuby está muerto.
IronRuby implementa hilos de Ruby como hilos nativos , donde "hilos nativos" en el caso del CLR obviamente significa "hilos CLR". IronRuby no les impone un bloqueo adicional, por lo tanto, deben ejecutarse en paralelo, siempre que su CLR lo admita.
Ruby.NET también implementa Ruby Threads como CLR Threads . Actualización: Ruby.NET está muerto.
Rubinius implementa hilos de rubí como hilos verdes dentro de su máquina virtual . Más precisamente: la VM Rubinius exporta una construcción de concurrencia / paralelismo / flujo de control no local muy ligera y muy flexible, llamada " Tarea ", y todas las demás construcciones de concurrencia (Hilos en esta discusión, pero también Continuaciones , Actores y otras cosas ) se implementan en Ruby puro, utilizando Tareas.
Sin embargo, Rubinius no puede (actualmente) programar subprocesos en paralelo, agregando que no es un gran problema: Rubinius ya puede ejecutar varias instancias de VM en varios subprocesos POSIX en paralelo , dentro de un proceso de Rubinius. Como los subprocesos se implementan realmente en Ruby, pueden, como cualquier otro objeto Ruby, ser serializados y enviados a una VM diferente en un subproceso POSIX diferente. (Ese es el mismo modelo que usa BEAM Erlang VM para la concurrencia de SMP. Ya está implementado para Rubinius Actors ).
Actualización : La información sobre Rubinius en esta respuesta es sobre la máquina virtual Shotgun, que ya no existe. La "nueva" máquina virtual C ++ no usa hilos verdes programados en varias máquinas virtuales (es decir, estilo Erlang / BEAM), usa una máquina virtual más tradicional con múltiples modelos de hilos de sistema operativo nativos, al igual que el empleado por, digamos, CLR, Mono , y casi todas las JVM.
MacRuby comenzó como un puerto de YARV en la parte superior de Objective-C Runtime y CoreFoundation y Cocoa Frameworks. Ahora se ha separado significativamente de YARV, pero AFAIK todavía comparte el mismo modelo de subprocesamiento con YARV . Actualización: MacRuby depende del recolector de basura de manzanas que se declara obsoleto y se eliminará en versiones posteriores de MacOSX, MacRuby está muerto.
Cardinal es una implementación de Ruby para la máquina virtual Parrot . Todavía no implementa subprocesos, sin embargo, cuando lo haga, probablemente los implemente como Parrot Threads . Actualización : Cardinal parece muy inactivo / muerto.
MagLev es una implementación de Ruby para GemStone / S Smalltalk VM . No tengo información sobre qué modelo de subprocesos utiliza GemStone / S, qué modelo de subprocesos utiliza MagLev o incluso si los subprocesos aún están implementados (probablemente no).
HotRuby no es una implementación completa de Ruby propia. Es una implementación de un bytecode YARV VM en JavaScript. HotRuby no admite subprocesos (¿todavía?) Y cuando lo hace, no podrán ejecutarse en paralelo, porque JavaScript no admite el verdadero paralelismo. Sin embargo, existe una versión ActionScript de HotRuby, y ActionScript podría admitir paralelismo. Actualización : HotRuby está muerto.
Desafortunadamente, solo dos de estas 11 implementaciones de Ruby están listas para la producción: MRI y JRuby.
Entonces, si desea verdaderos hilos paralelos, JRuby es actualmente su única opción, no es que sea mala: JRuby es en realidad más rápido que MRI, y posiblemente más estable.
De lo contrario, la solución "clásica" de Ruby es utilizar procesos en lugar de hilos para el paralelismo. La Biblioteca de Ruby Core contiene el Process
módulo con el Process.fork
método que hace que sea muy fácil bifurcar otro proceso de Ruby. Además, la Biblioteca estándar de Ruby contiene la biblioteca de
Ruby distribuido (dRuby / dRb) , que permite que el código de Ruby se distribuya trivialmente en múltiples procesos, no solo en la misma máquina sino también en la red.
Ruby 1.8 solo tiene hilos verdes, no hay forma de crear un hilo real de "nivel de sistema operativo". Pero, ruby 1.9 tendrá una nueva característica llamada fibras, que le permitirá crear hilos reales a nivel del sistema operativo. Desafortunadamente, Ruby 1.9 todavía está en beta, está programado para ser estable en un par de meses.
Otra alternativa es usar JRuby. JRuby implementa hilos como cabezas de nivel de sistema operativo, no hay "hilos verdes" en él. La última versión de JRuby es 1.1.4 y es equivalente a Ruby 1.8
Depende de la implementación:
Ruby tiene cierres como Blocks
, lambdas
y Procs
. Para aprovechar al máximo los cierres y los múltiples núcleos en JRuby, los ejecutores de Java son útiles; para MacRuby me gustan las colas de GCD .
Tenga en cuenta que poder crear hilos reales de "nivel de sistema operativo" no implica que pueda usar múltiples núcleos de CPU para el procesamiento paralelo. Mira los ejemplos a continuación.
Este es el resultado de un programa simple de Ruby que usa 3 hilos usando Ruby 2.1.0:
(jalcazar@mac ~)$ ps -M 69877
USER PID TT %CPU STAT PRI STIME UTIME COMMAND
jalcazar 69877 s002 0.0 S 31T 0:00.01 0:00.04 /Users/jalcazar/.rvm/rubies/ruby-2.1.0/bin/ruby threads.rb
69877 0.0 S 31T 0:00.01 0:00.00
69877 33.4 S 31T 0:00.01 0:08.73
69877 43.1 S 31T 0:00.01 0:08.73
69877 22.8 R 31T 0:00.01 0:08.65
Como puede ver aquí, hay cuatro subprocesos del sistema operativo, sin embargo, solo se R
está ejecutando el que tiene estado . Esto se debe a una limitación en cómo se implementan los hilos de Ruby.
Mismo programa, ahora con JRuby. Puede ver tres subprocesos con estado R
, lo que significa que se ejecutan en paralelo.
(jalcazar@mac ~)$ ps -M 72286
USER PID TT %CPU STAT PRI STIME UTIME COMMAND
jalcazar 72286 s002 0.0 S 31T 0:00.01 0:00.01 /Library/Java/JavaVirtualMachines/jdk1.7.0_25.jdk/Contents/Home/bin/java -Djdk.home= -Djruby.home=/Users/jalcazar/.rvm/rubies/jruby-1.7.10 -Djruby.script=jruby -Djruby.shell=/bin/sh -Djffi.boot.library.path=/Users/jalcazar/.rvm/rubies/jruby-1.7.10/lib/jni:/Users/jalcazar/.rvm/rubies/jruby-1.7.10/lib/jni/Darwin -Xss2048k -Dsun.java.command=org.jruby.Main -cp -Xbootclasspath/a:/Users/jalcazar/.rvm/rubies/jruby-1.7.10/lib/jruby.jar -Xmx1924M -XX:PermSize=992m -Dfile.encoding=UTF-8 org/jruby/Main threads.rb
72286 0.0 S 31T 0:00.00 0:00.00
72286 0.0 S 33T 0:00.00 0:00.00
72286 0.0 S 31T 0:00.09 0:02.34
72286 7.9 S 31T 0:00.15 0:04.63
72286 0.0 S 31T 0:00.00 0:00.00
72286 0.0 S 31T 0:00.00 0:00.00
72286 0.0 S 31T 0:00.00 0:00.00
72286 0.0 S 31T 0:00.04 0:01.68
72286 0.0 S 31T 0:00.03 0:01.54
72286 0.0 S 31T 0:00.00 0:00.00
72286 0.0 S 31T 0:00.01 0:00.01
72286 0.0 S 31T 0:00.00 0:00.01
72286 0.0 S 31T 0:00.00 0:00.03
72286 74.2 R 31T 0:09.21 0:37.73
72286 72.4 R 31T 0:09.24 0:37.71
72286 74.7 R 31T 0:09.24 0:37.80
El mismo programa, ahora con MacRuby. También hay tres hilos que se ejecutan en paralelo. Esto se debe a que los subprocesos de MacRuby son subprocesos POSIX ( subprocesos reales de "nivel de sistema operativo" ) y no hay GVL
(jalcazar@mac ~)$ ps -M 38293
USER PID TT %CPU STAT PRI STIME UTIME COMMAND
jalcazar 38293 s002 0.0 R 0T 0:00.02 0:00.10 /Users/jalcazar/.rvm/rubies/macruby-0.12/usr/bin/macruby threads.rb
38293 0.0 S 33T 0:00.00 0:00.00
38293 100.0 R 31T 0:00.04 0:21.92
38293 100.0 R 31T 0:00.04 0:21.95
38293 100.0 R 31T 0:00.04 0:21.99
Una vez más, el mismo programa pero ahora con la buena resonancia magnética. Debido al hecho de que esta implementación usa hilos verdes, solo aparece un hilo
(jalcazar@mac ~)$ ps -M 70032
USER PID TT %CPU STAT PRI STIME UTIME COMMAND
jalcazar 70032 s002 100.0 R 31T 0:00.08 0:26.62 /Users/jalcazar/.rvm/rubies/ruby-1.8.7-p374/bin/ruby threads.rb
Si está interesado en los subprocesos múltiples de Ruby, puede encontrar interesante mi informe Depuración de programas paralelos utilizando controladores de fork .
Para una descripción más general de las partes internas de Ruby, Ruby Under a Microscope es una buena lectura.
Además, Ruby Threads y el Global Interpreter Lock en C en Omniref explican en el código fuente por qué los hilos Ruby no se ejecutan en paralelo.
Dejaré que el "Monitor del sistema" responda esta pregunta. Estoy ejecutando el mismo código (a continuación, que calcula los números primos) con 8 hilos Ruby ejecutándose en una máquina i7 (4 hyperthreaded-core) en ambos casos ... la primera ejecución es con:
jruby 1.5.6 (ruby 1.8.7 patchlevel 249) (2014-02-03 6586) (OpenJDK 64-Bit Server VM 1.7.0_75) [amd64-java]
El segundo es con:
ruby 2.1.2p95 (08/05/2014) [x86_64-linux-gnu]
Curiosamente, la CPU es más alta para los hilos JRuby, pero el tiempo de finalización es ligeramente más corto para el Ruby interpretado. Es un poco difícil de distinguir del gráfico, pero la segunda ejecución (interpretada por Ruby) usa aproximadamente la mitad de las CPU (¿no hay hyperthreading?)
def eratosthenes(n)
nums = [nil, nil, *2..n]
(2..Math.sqrt(n)).each do |i|
(i**2..n).step(i){|m| nums[m] = nil} if nums[i]
end
nums.compact
end
MAX_PRIME=10000000
THREADS=8
threads = []
1.upto(THREADS) do |num|
puts "Starting thread #{num}"
threads[num]=Thread.new { eratosthenes MAX_PRIME }
end
1.upto(THREADS) do |num|
threads[num].join
end
Si está utilizando MRI, puede escribir el código en C como una extensión o utilizando la gema en línea de rubí.
Si realmente necesita paralelismo en Ruby para un sistema de nivel de producción (donde no puede emplear una versión beta), los procesos son probablemente una mejor alternativa.
Pero, definitivamente vale la pena probar hilos bajo JRuby primero.
Además, si está interesado en el futuro de subprocesos bajo Ruby, este artículo puede resultarle útil.
Parallel.map(['a','b','c'], :in_processes=>3){...
Aquí hay información sobre Rinda, que es la implementación de Ruby de Linda (paradigma de procesamiento paralelo y computación distribuida) http://charmalloc.blogspot.com/2009/12/linda-tuples-rinda-drb-parallel.html
Como no se pudo editar esa respuesta, agregue una nueva respuesta aquí.
Actualización (2017-05-08)
Este artículo es muy antiguo y la información no sigue la banda de rodadura actual (2017). A continuación se incluye un suplemento:
Opal es un compilador de fuente a fuente de Ruby a JavaScript. También tiene una implementación de Ruby corelib, actualmente es un desarrollo muy activo, y existe una gran cantidad de marco (frontend) trabajado en él. y producción lista. Debido a que se basa en javascript, no admite subprocesos paralelos.
truffleruby es una implementación de alto rendimiento del lenguaje de programación Ruby. Construido en el GraalVM por Oracle Labs, TruffleRuby es una bifurcación de JRuby, que lo combina con el código del proyecto Rubinius, y que también contiene código de la implementación estándar de Ruby, MRI, aún desarrollo en vivo, no listo para producción. Parece que esta versión de Ruby nació para el rendimiento, no sé si admite subprocesos paralelos, pero creo que debería.