Este golf requiere un cálculo factorial que se divida entre múltiples hilos o procesos.
Algunos idiomas hacen que esto sea más fácil de coordinar que otros, por lo que es un lenguaje agnóstico. Se proporciona un código de ejemplo no protegido, pero debe desarrollar su propio algoritmo.
El objetivo del concurso es ver quién puede encontrar el algoritmo factorial multinúcleo más corto (en bytes, no segundos) para calcular N! medido por los votos cuando se cierra el concurso. Debería haber una ventaja multinúcleo, por lo que necesitaremos que funcione para N ~ 10,000. Los votantes deben votar si el autor no proporciona una explicación válida de cómo distribuye el trabajo entre los procesadores / núcleos y votar en base a la concisión del golf.
Por curiosidad, publique algunos números de rendimiento. Puede haber una compensación entre el rendimiento y el puntaje de golf en algún momento, vaya con el golf siempre que cumpla con los requisitos. Me gustaría saber cuándo ocurre esto.
Puede utilizar bibliotecas de gran número de núcleos individuales normalmente disponibles. Por ejemplo, perl generalmente se instala con bigint. Sin embargo, tenga en cuenta que simplemente llamar a una función factorial proporcionada por el sistema normalmente no dividirá el trabajo en múltiples núcleos.
Debe aceptar de STDIN o ARGV la entrada N y la salida a STDOUT el valor de N !. Opcionalmente, puede usar un segundo parámetro de entrada para proporcionar también el número de procesadores / núcleos al programa para que no haga lo que verá a continuación :-) O puede diseñar explícitamente para 2, 4, lo que tenga disponible.
Publicaré mi propio ejemplo de perl Oddball a continuación, presentado previamente en Stack Overflow en Algoritmos factoriales en diferentes idiomas . No es golf. Se presentaron muchos otros ejemplos, muchos de ellos de golf pero muchos no. Debido a las licencias compartidas, siéntase libre de usar el código en cualquier ejemplo en el enlace anterior como punto de partida.
El rendimiento en mi ejemplo es mediocre por varias razones: utiliza demasiados procesos, demasiada conversión de cadenas / bigint. Como dije, es un ejemplo intencionalmente extraño. ¡Calculará 5000! en menos de 10 segundos en una máquina de 4 núcleos aquí. Sin embargo, ¡un dos forro más obvio para / próximo ciclo puede hacer 5000! en uno de los cuatro procesadores en 3.6s.
Definitivamente tendrás que hacerlo mejor que esto:
#!/usr/bin/perl -w
use strict;
use bigint;
die "usage: f.perl N (outputs N!)" unless ($ARGV[0] > 1);
print STDOUT &main::rangeProduct(1,$ARGV[0])."\n";
sub main::rangeProduct {
my($l, $h) = @_;
return $l if ($l==$h);
return $l*$h if ($l==($h-1));
# arghhh - multiplying more than 2 numbers at a time is too much work
# find the midpoint and split the work up :-)
my $m = int(($h+$l)/2);
my $pid = open(my $KID, "-|");
if ($pid){ # parent
my $X = &main::rangeProduct($l,$m);
my $Y = <$KID>;
chomp($Y);
close($KID);
die "kid failed" unless defined $Y;
return $X*$Y;
} else {
# kid
print STDOUT &main::rangeProduct($m+1,$h)."\n";
exit(0);
}
}
Mi interés en esto es simplemente (1) aliviar el aburrimiento; y (2) aprender algo nuevo. Este no es un problema de tarea o investigación para mí.
¡Buena suerte!
Respuestas:
Mathematica
Una función con capacidad paralela:
Donde g es
Identity
oParallelize
dependiendo del tipo de proceso requeridoPara la prueba de tiempo, modificaremos ligeramente la función, por lo que devuelve la hora real del reloj.
Y probamos ambos modos (desde 10 ^ 5 hasta 9 * 10 ^ 5): (solo dos núcleos aquí)
Resultado:
fuente
Haskell:
209200198177 caracteres176167 fuente +3310 compilador flagEsta solución es bastante tonta. Aplica el producto en paralelo a un valor de tipo
[[Integer]]
, donde las listas internas tienen como máximo dos elementos. Una vez que la lista externa se reduce a un máximo de 2 listas, la aplanamos y tomamos el producto directamente. Y sí, el verificador de tipo necesita algo anotado con Integer, de lo contrario no se compilará.(Siéntase libre de leer la parte media de
f
entreconcat
ys
como "hasta que me guste la longitud")Parecía que iban a estar bastante bien, ya que parMap de Control.Parallel.Strategies hace que sea bastante fácil explotar esto en múltiples subprocesos. Sin embargo, parece que GHC 7 requiere 33 caracteres en las opciones de línea de comando y variables de entorno para permitir que el tiempo de ejecución enhebrado use múltiples núcleos (que incluí en el total).A menos que me falte algo, lo que definitivamente esposible. ( Actualización : el tiempo de ejecución de GHC roscado parece usar subprocesos N-1, donde N es el número de núcleos, por lo que no es necesario jugar con las opciones de tiempo de ejecución).Compilar:
Sin embargo, el tiempo de ejecución fue bastante bueno teniendo en cuenta la cantidad ridícula de evaluaciones paralelas que se generaron y que no compilé con -O2. ¡Por 50000! en una MacBook de doble núcleo, obtengo:
Tiempos totales para algunos valores diferentes, la primera columna es el paralelo golfizado, la segunda es la versión secuencial ingenua:
Como referencia, la versión secuencial ingenua es esta (que se compiló con -O2):
fuente
Ruby - 111 + 56 = 167 caracteres
Este es un script de dos archivos, el archivo principal (
fact.rb
):el archivo extra (
f2.rb
):Simplemente toma el número de procesos y el número para calcular como argumentos y divide el trabajo en rangos que cada proceso puede calcular individualmente. Luego multiplica los resultados al final.
Esto realmente muestra cuánto más lento es Rubinius para YARV:
Rubinio:
Ruby1.9.2:
(Tenga en cuenta el extra
0
)fuente
inject(:+)
. Aquí está el ejemplo de los documentos:(5..10).reduce(:+)
.8
donde debería haber habido un*
si alguien tenía problemas para ejecutar esto.Java,
523 519 434 430429 caracteresLos dos 4 en la línea final son el número de hilos a usar.
50000! probado con el siguiente marco (versión no protegida de la versión original y con un puñado menos de malas prácticas, aunque todavía tiene muchas) da (en mi máquina Linux de 4 núcleos) veces
Tenga en cuenta que repetí la prueba con un hilo de imparcialidad porque el jit podría haberse calentado.
Java con bigints no es el lenguaje adecuado para jugar al golf (mira lo que tengo que hacer solo para construir las cosas miserables, porque el constructor que lleva mucho tiempo es privado), pero bueno.
Debería ser completamente obvio a partir del código no protegido cómo divide el trabajo: cada subproceso multiplica un módulo de clase de equivalencia por el número de subprocesos. El punto clave es que cada hilo hace aproximadamente la misma cantidad de trabajo.
fuente
CSharp -
206215 caracteresDivide el cálculo con la funcionalidad C # Parallel.For ().
Editar; Olvidé la cerradura
Tiempos de ejecución:
fuente
Perl, 140
Toma
N
de entrada estándar.caracteristicas:
Punto de referencia:
fuente
Scala (
345266244232214 caracteres)Usando actores:
Editar: referencias eliminadas
System.currentTimeMillis()
, eliminadasa(1).toInt
, cambiadas deList.range
ax to y
Editar 2: cambió el
while
bucle a afor
, cambió el pliegue izquierdo a una función de lista que hace lo mismo, confiando en las conversiones de tipo implícitas para que elBigInt
tipo de 6 caracteres aparezca solo una vez, cambió println para imprimirEdición 3: descubrió cómo hacer varias declaraciones en Scala
Edición 4: varias optimizaciones que he aprendido desde que hice esto por primera vez
Versión sin golf:
fuente
Scala-2.9.0 170
sin golf:
El factorial de 10 se calcula en 4 núcleos generando 4 listas:
que se multiplican en paralelo Hubiera sido un enfoque más simple para distribuir los números:
Pero la distribución no sería tan buena: los números más pequeños terminarían en la misma lista, los más altos en otra, lo que llevaría a un cálculo más largo en la última lista (para N altas, el último hilo no estaría casi vacío) , pero al menos contienen elementos (N / núcleos) -cores.
Scala en la versión 2.9 contiene colecciones paralelas, que manejan la invocación paralela ellos mismos.
fuente
Erlang - 295 caracteres.
Lo primero que he escrito en Erlang, así que no me sorprendería si alguien pudiera reducir a la mitad esto fácilmente:
Utiliza el mismo modelo de subprocesos que mi entrada anterior de Ruby: divide el rango en subrangos y multiplica los rangos en hilos separados y luego multiplica los resultados en el hilo principal.
No pude descubrir cómo hacer que el escript funcione, así que solo guarde como
f.erl
y abra erl y ejecute:con sustituciones apropiadas.
Obtuve alrededor de 8 segundos para 50000 en 2 procesos y 10 segundos para 1 proceso, en mi MacBook Air (doble núcleo).
Nota: Acabo de notar que se congela si intenta más procesos que el número para factorizar.
fuente