¿Ruby tiene multiproceso real?

295

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?

Skolima
fuente

Respuestas:

612

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).

  1. 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.

  2. 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.

  3. 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)

  4. XRuby también implementa Ruby Threads como JVM Threads . Actualización : XRuby está muerto.

  5. 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.

  6. Ruby.NET también implementa Ruby Threads como CLR Threads . Actualización: Ruby.NET está muerto.

  7. 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.

  8. 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.

  9. 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.

  10. 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).

  11. 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 Processmó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.

Jörg W Mittag
fuente
1
pero el uso de fork interrumpirá el uso en jruby ... solo digo
akostadinov el
1
Esta es una respuesta genial. Sin embargo, está sujeto a una gran cantidad de enlaces podridos. Sin embargo, no sé a dónde se han movido estos recursos.
BlackVegetable
28

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

Josh Moore
fuente
35
Es falso que Ruby 1.8 solo tenga hilos verdes, varias implementaciones de Ruby 1.8 tienen hilos nativos: JRuby, XRuby, Ruby.NET y IronRuby. Las fibras no permiten la creación de hilos nativos, son más livianos que los hilos. En realidad son semi-corutinas, es decir, son cooperativas.
Jörg W Mittag
19
Creo que es bastante obvio por la respuesta de Josh que se refiere a Ruby 1.8 el tiempo de ejecución, también conocido como MRI, y no Ruby 1.8 el idioma, cuando dice Ruby 1.8.
Theo
@Theo También es obvio que estropea los conceptos en su respuesta. Las fibras no son una forma de crear hilos nativos, como ya se mencionó, son cosas aún más livianas que los hilos y el cruby actual tiene hilos nativos pero con GIL.
Foo Bar Zoo
8

Depende de la implementación:

  • MRI no tiene, YARV está más cerca.
  • JRuby y MacRuby tienen.




Ruby tiene cierres como Blocks, lambdasy 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 Restá 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.

usuario454322
fuente
Por RMI, ¿te refieres a MRI?
Mayuresh Srivastava
4

¿Qué tal usar drb ? No es multihilo real, sino comunicación entre varios procesos, pero puede usarlo ahora en 1.8 y tiene una fricción bastante baja.

ujh
fuente
3

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?)

ingrese la descripción de la imagen aquí

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
Tortas Groovy
fuente
1

Si está utilizando MRI, puede escribir el código en C como una extensión o utilizando la gema en línea de rubí.


fuente
1

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.

Pascal
fuente
JRuby es una buena opción. Para el procesamiento en paralelo utilizando procesos, me gusta github.com/grosser/parallel Parallel.map(['a','b','c'], :in_processes=>3){...
user454322
1

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:

  1. 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.

  2. 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.

zw963
fuente