Fattoriali di Forking


12

Questo golf richiede un calcolo fattoriale suddiviso tra più thread o processi.

Alcune lingue rendono questo più facile da coordinare rispetto ad altri, quindi è lang agnostico. Viene fornito un codice di esempio non registrato, ma è necessario sviluppare un proprio algoritmo.

L'obiettivo del concorso è vedere chi può elaborare l'algoritmo fattoriale multicore più breve (in byte, non secondi) per il calcolo di N! come misurato dai voti alla chiusura del concorso. Dovrebbe esserci un vantaggio multicore, quindi richiederemo che funzioni per N ~ 10.000. Gli elettori dovrebbero votare verso il basso se l'autore non fornisce una spiegazione valida di come distribuisce il lavoro tra processori / core e votare in base alla concisione del golf.

Per curiosità, si prega di pubblicare alcuni numeri di performance. Ad un certo punto potrebbe esserci un compromesso tra prestazioni e punteggio del golf, andare con il golf purché soddisfi i requisiti. Sarei curioso di sapere quando questo accadrà.

È possibile utilizzare librerie di interi grandi single core normalmente disponibili. Ad esempio, perl è solitamente installato con bigint. Tuttavia, si noti che il semplice richiamo di una funzione fattoriale fornita dal sistema normalmente non suddivide il lavoro su più core.

È necessario accettare da STDIN o ARGV l'ingresso N e trasmettere a STDOUT il valore di N !. Puoi facoltativamente utilizzare un secondo parametro di input per fornire anche il numero di processori / core al programma in modo che non faccia ciò che vedrai di seguito :-) O puoi progettare esplicitamente per 2, 4, qualunque cosa tu abbia a disposizione.

Pubblicherò il mio esempio perl di oddball di seguito, precedentemente inviato su Stack Overflow in Algoritmi fattoriali in diverse lingue . Non è golf. Sono stati presentati numerosi altri esempi, molti dei quali golf, ma molti no. A causa delle licenze simili alle condivisioni, sentiti libero di usare il codice in tutti gli esempi del link sopra come punto di partenza.

Le prestazioni nel mio esempio sono scarse per una serie di ragioni: utilizza troppi processi, troppa conversione stringa / bigint. Come ho detto, è un esempio intenzionalmente strano. Calcolerà 5000! in meno di 10 secondi su una macchina a 4 core qui. Tuttavia, un liner più ovvio per il prossimo / prossimo ciclo può fare 5000! su uno dei quattro processori in 3.6s.

Dovrai sicuramente fare di meglio:

#!/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);
    }
}

Il mio interesse per questo è semplicemente (1) alleviare la noia; e (2) imparare qualcosa di nuovo. Per me questo non è un problema di compiti o di ricerca.

In bocca al lupo!


10
Non puoi contare il codice più corto in base ai voti e il requisito del golf e del multi-thread sembrano andare male insieme.
aaaaaaaaaaaa

Il mio antico quaderno single core può fare 10000! in meno di 0,2 secondi in Python.
Gnibbler,

Il multithreading di un processo associato alla CPU lo rallenta quasi sempre. Tutto quello che stai facendo è aggiungere un sovraccarico con un guadagno in termini di prestazioni scarso o nullo. Il multi-threading è per I / O-wait.
mellamokb,

2
@mellamokb: supplico di differire per i sistemi multi-core.
Joey,

@Joey: Ah. Perso quel piccolo dettaglio: s Concordato
mellamokb

Risposte:


7

matematica

Una funzione parallela:

 f[n_, g_] := g[Product[N@i, {i, 1, n, 2}] Product[N@i, {i, 2, n, 2}]]

Dove g è Identityo in Parallelizebase al tipo di processo richiesto

Per il test di temporizzazione, modificheremo leggermente la funzione, in modo che restituisca l'ora reale.

f[n_, g_] := First@AbsoluteTiming[g[Product[N@i,{i,1,n,2}] Product[N@i,{i,2,n,2}]]]

E testiamo entrambe le modalità (da 10 ^ 5 fino a 9 * 10 ^ 5): (solo due kernel qui)

ListLinePlot[{Table[f[i, Identity],    {i, 100000, 900000, 100000}], 
              Table[f[i, Parallelize], {i, 100000, 900000, 100000}]}]   

Risultato: inserisci qui la descrizione dell'immagine


Ti manca un] nella prima riga di codice? Sembra squilibrato.
Peter Taylor,

@Peter Grazie, l'ultimo "]" non si è fatto strada nel buffer di copia. Corretto.
Dr. belisarius,

1
Questo sembra essere il più breve. Sembra anche il più veloce, a meno che non stia leggendo male qualcosa. Non mi iscrivo più a Mathematica, quindi non posso verificare. Grazie per aver partecipato.
Paul,

7

Haskell: 209 200 198 177 caratteri

176 167 source + 33 10 flag compilatore

Questa soluzione è piuttosto sciocca. Applica il prodotto parallelamente a un valore di tipo [[Integer]], in cui gli elenchi interni sono lunghi al massimo due elementi. Una volta che l'elenco esterno è sceso al massimo a 2 elenchi, lo appiattiamo e prendiamo direttamente il prodotto. E sì, il controllo del tipo ha bisogno di qualcosa di annotato con Integer, altrimenti non verrà compilato.

import Control.Parallel.Strategies
s(x:y:z)=[[x,y::Integer]]++s z;s x=[x]
p=product
f n=p$concat$(until((<3).length)$s.parMap rseq p)$s[1..n]
main=interact$show.f.read

(Sentiti libero di leggere la parte centrale ftra concate scome "fino a quando non ho il cuore in lunghezza")

Le cose sembravano andare abbastanza bene dal momento che parMap di Control.Parallel.Strategies rende abbastanza facile coltivare questo su più thread. Tuttavia, sembra che GHC 7 richieda ben 33 caratteri nelle opzioni della riga di comando e nelle varianti di ambiente per consentire effettivamente al runtime threadizzato di utilizzare più core (che ho incluso nel totale). A meno che non mi manchi qualcosa, il che è sicuramente possibile . ( Aggiornamento : il runtime GHC con thread sembra utilizzare thread N-1, dove N è il numero di core, quindi non è necessario giocherellare con le opzioni di runtime.)

Compilare:

ghc -threaded prog.hs

Il tempo di esecuzione, tuttavia, è stato piuttosto buono considerando il ridicolo numero di valutazioni parallele innescate e che non ho compilato con -O2. Per 50000! su un MacBook dual-core, ottengo:

SPARKS: 50020 (29020 converted, 1925 pruned)

INIT  time    0.00s  (  0.00s elapsed)
MUT   time    0.20s  (  0.19s elapsed)
GC    time    0.12s  (  0.07s elapsed)
EXIT  time    0.00s  (  0.00s elapsed)
Total time    0.31s  (  0.27s elapsed)

Tempi totali per pochi valori diversi, la prima colonna è il parallelo golfizzato, la seconda è la versione sequenziale ingenua:

          Parallel   Sequential
 10000!      0.03s        0.04s
 50000!      0.27s        0.78s
100000!      0.74s        3.08s
500000!      7.04s       86.51s

Per riferimento, la versione sequenziale ingenua è questa (che è stata compilata con -O2):

factorial :: Integer -> Integer
factorial n = product [1..n]
main = interact $ show.factorial.read

1
IMO, non devi contare gli arg per il compilatore e l'interprete.
FUZxxl

@FUZxxl: Normalmente concordo, ma questo problema ha richiesto specificatamente che funzionasse in più thread o processi, e quei flag sono necessari per farlo accadere (almeno con GHC 7.0.2, dall'ultima piattaforma Haskell).

6

Rubino - 111 + 56 = 167 caratteri

Questo è uno script a due file, il file principale ( fact.rb):

c,n=*$*.map(&:to_i)
p=(0...c).map{|k|IO.popen("ruby f2.rb #{k} #{c} #{n}")}
p p.map{|l|l.read.to_i}.inject(:*)

il file extra ( f2.rb):

c,h,n=*$*.map(&:to_i)
p (c*n/h+1..(c+1)*n/h).inject(:*)

Prende semplicemente il numero di processi e il numero da calcolare come args e divide il lavoro in intervalli che ogni processo può calcolare individualmente. Quindi moltiplica i risultati alla fine.

Questo dimostra davvero quanto Rubinius sia più lento di YARV:

Rubinius:

time ruby fact.rb 5 5000 #=> 61.84s

Ruby1.9.2:

time ruby fact.rb 5 50000 #=> 3.09s

(Nota extra 0)


1
inject può prendere un simbolo come argomento, quindi puoi salvare un personaggio usando inject(:+). Ecco l'esempio dalla documentazione: (5..10).reduce(:+).
Michael Kohl,

@Michael: Grazie :). Ho anche notato che avevo un posto 8dove avrebbe dovuto esserci un *qualcuno se avesse avuto problemi a farlo .
Nemo157,

6

Java, 523 519 434 430 429 caratteri

import java.math.*;public class G extends Thread{BigInteger o,i,r=BigInteger.ONE,h;G g;G(BigInteger O,int
I,int n){o=O;i=new BigInteger(""+I);if(n>1)g=new G(O.subtract(r),I,n-1);h=n==I?i:r;start();}public void
run(){while(o.signum()>0){r=r.multiply(o);o=o.subtract(i);}try{g.join();r=r.multiply(g.r);}catch(Exception
e){}if(h==i)System.out.println(r);}public static void main(String[] args){new G(new BigInteger(args[0]),4,4);}}

I due 4 nell'ultima riga sono il numero di thread da usare.

50000! testato con il seguente framework (versione non golfata della versione originale e con poche pratiche sbagliate - sebbene ne abbia ancora molte) dà (sulla mia macchina Linux a 4 core) tempi

7685ms
2338ms
1361ms
1093ms
7724ms

Si noti che ho ripetuto il test con un thread per correttezza perché il jit potrebbe essersi riscaldato.

import java.math.*;

public class ForkingFactorials extends Thread { // Bad practice!
    private BigInteger off, inc;
    private volatile BigInteger res;

    private ForkingFactorials(int off, int inc) {
        this.off = new BigInteger(Integer.toString(off));
        this.inc = new BigInteger(Integer.toString(inc));
    }

    public void run() {
        BigInteger p = new BigInteger("1");
        while (off.signum() > 0) {
            p = p.multiply(off);
            off = off.subtract(inc);
        }
        res = p;
    }

    public static void main(String[] args) throws Exception {
        int n = Integer.parseInt(args[0]);
        System.out.println(f(n, 1));
        System.out.println(f(n, 2));
        System.out.println(f(n, 3));
        System.out.println(f(n, 4));
        System.out.println(f(n, 1));
    }

    private static BigInteger f(int n, int numThreads) throws Exception {
        long now = System.currentTimeMillis();
        ForkingFactorials[] th = new ForkingFactorials[numThreads];
        for (int i = 0; i < n && i < numThreads; i++) {
            th[i] = new ForkingFactorials(n-i, numThreads);
            th[i].start();
        }
        BigInteger f = new BigInteger("1");
        for (int i = 0; i < n && i < numThreads; i++) {
            th[i].join();
            f = f.multiply(th[i].res);
        }
        long t = System.currentTimeMillis() - now;
        System.err.println("Took " + t + "ms");
        return f;
    }
}

Java con le origini non è la lingua giusta per giocare a golf (guarda cosa devo fare solo per costruire le cose miserabili, perché il costruttore che impiega molto tempo è privato), ma ehi.

Dal codice ungolf dovrebbe essere del tutto evidente come si rompe il lavoro: ogni thread moltiplica una classe di equivalenza modulo per il numero di thread. Il punto chiave è che ogni thread fa all'incirca la stessa quantità di lavoro.


5

CSharp - 206 215 caratteri

using System;using System.Numerics;using System.Threading.Tasks;class a{static void Main(){var n=int.Parse(Console.ReadLine());var r=new BigInteger(1);Parallel.For(1,n+1,i=>{lock(this)r*=i;});Console.WriteLine(r);}}

Suddivide il calcolo con la funzionalità C # Parallel.For ().

Modificare; Ho dimenticato il lucchetto

Tempi di esecuzione:

n = 10,000, time: 59ms.
n = 20,000, time: 50ms.
n = 30,000, time: 38ms.
n = 40,000, time: 100ms.
n = 50,000, time: 139ms.
n = 60,000, time: 164ms.
n = 70,000, time: 222ms.
n = 80,000, time: 266ms.
n = 90,000, time: 401ms.
n = 100,000, time: 424ms.
n = 110,000, time: 501ms.
n = 120,000, time: 583ms.
n = 130,000, time: 659ms.
n = 140,000, time: 832ms.
n = 150,000, time: 1143ms.
n = 160,000, time: 804ms.
n = 170,000, time: 653ms.
n = 180,000, time: 1031ms.
n = 190,000, time: 1034ms.
n = 200,000, time: 1765ms.
n = 210,000, time: 1059ms.
n = 220,000, time: 1214ms.
n = 230,000, time: 1362ms.
n = 240,000, time: 2737ms.
n = 250,000, time: 1761ms.
n = 260,000, time: 1823ms.
n = 270,000, time: 3357ms.
n = 280,000, time: 2110ms.

4

Perl, 140

Prende Ndall'input standard.

use bigint;$m=<>;open A,'>',
undef;$i=$p=fork&&1;$n=++$i;
{$i+=2;$n*=$i,redo if$i<=$m}
if($p){wait;seek A,0,0;$_=<A
>;print$n*$_}else{print A$n}

Caratteristiche:

  • computazione divisa: pari su un lato e probabilità sull'altro (qualcosa di più complesso di quello avrebbe bisogno di molti caratteri per bilanciare il carico di calcolo in modo appropriato.
  • IPC utilizzando un file anonimo condiviso.

Prova delle prestazioni:

  • 10000! è stampato in biforcuta 2.3s, 3.4s stappata
  • 100000! è stampato in 5'08.8 biforcati, 7'07.9 non stappato

4

Scala ( 345 266 244 232 214 caratteri)

Utilizzando attori:

object F extends App{import actors.Actor._;var(t,c,r)=(args(1).toInt,self,1:BigInt);val n=args(0).toInt min t;for(i<-0 to n-1)actor{c!(i*t/n+1 to(i+1)*t/n).product};for(i<-1 to n)receive{case m:Int=>r*=m};print(r)}

Modifica: rimossi i riferimenti a System.currentTimeMillis(), ignorati a(1).toInt, modificati da List.rangeax to y

Modifica 2: cambiato il whileciclo in a for, cambiato la piega a sinistra in una funzione di elenco che fa la stessa cosa, basandosi su conversioni di tipo implicite in modo che il BigInttipo di 6 caratteri appaia solo una volta, cambiato println per stampare

Modifica 3: scopri come eseguire più dichiarazioni in Scala

Modifica 4: varie ottimizzazioni che ho imparato da quando l'ho fatto per la prima volta

Versione non golfata:

import actors.Actor._
object ForkingFactorials extends App
{
    var (target,caller,result)=(args(1).toInt,self,1:BigInt)
    val numthreads=args(0).toInt min target
    for(i<-0 to numthreads-1)
        actor
        {
            caller ! (i*target/numthreads+1 to(i+1)*target/numthreads+1).product
        }
    for(i<-1 to numthreads)
        receive
        {
            case m:Int=>result*=m
        }
    print(result)
}

3

Scala-2.9.0 170

object P extends App{
def d(n:Int,c:Int)=(for(i<-1 to c)yield(i to n by c)).par
println((BigInt(1)/: d(args(0).toInt,args(1).toInt).map(x=>(BigInt(1)/: x)(_*_)))(_*_))}

ungolfed:

object ParallelFactorials extends App
{
  def distribute (n: Int, cores: Int) = {
    val factorgroup = for (i <- 1 to cores) 
      yield (i to n by cores)
    factorgroup.par
  }

  val parallellist = distribute (args(0).toInt, args(1).toInt)

  println ((BigInt (1) /: parallellist.map (x => (BigInt(1) /: x) (_ * _)))(_ * _))

}

Il fattoriale di 10 viene calcolato su 4 core generando 4 liste:

  • 1 5 9
  • 2 6 10
  • 3 7
  • 4 8

che si moltiplicano in parallelo. Ci sarebbe stato un approccio più semplice per distribuire i numeri:

 (1 to n).sliding ((n/cores), (n/cores) 
  • 1 2 3
  • 4 5 6
  • 7 8 9
  • 10

Ma la distribuzione non sarebbe così buona - i numeri più piccoli finirebbero tutti nella stessa lista, il più alto in un'altra, portando al calcolo più lungo nell'ultima lista (per N alti, l'ultimo thread non sarebbe quasi vuoto , ma almeno contiene (N / core) -cores elementi.

Scala nella versione 2.9 contiene Collezioni parallele, che gestiscono da sole l'invocazione parallela.


2

Erlang - 295 caratteri.

Prima cosa che abbia mai scritto in Erlang, quindi non sarei sorpreso se qualcuno potesse dimezzarlo facilmente:

-module(f).
-export([m/2,f/4]).
m(N,C)->g(N,C,C,[]).
r([],B)->B;
r(A,B)->receive{F,V}->r(lists:delete(F,A),V*B)end.
s(H,L)->spawn(f,f,[self(),H,L,1]).
g(N,1,H,A)->r([s(N div H,1)|A],1);
g(N,C,H,A)->g(N,C-1,H,[s(N*C div H,N*(C-1) div H)|A]).
f(P,H,H,A)->P!{self(),A};
f(P,H,L,A)->f(P,H-1,L,A*H).

Utilizza lo stesso modello di threading della mia precedente voce Ruby: divide l'intervallo in sottointervallo e moltiplica gli intervalli in thread separati, quindi moltiplica i risultati nel thread principale.

Non sono riuscito a capire come far funzionare escript, quindi salva semplicemente as f.erle apri erl ed esegui:

c(f).
f:m(NUM_TO_CALC, NUM_OF_PROCESSES).

con opportune sostituzioni.

Ho ottenuto circa 8 secondi per 50000 in 2 processi e 10 secondi per 1 processo, sul mio MacBook Air (dual-core).

Nota: ho appena notato che si blocca se si tenta più processi rispetto al numero da fattorizzare.

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.