2013-06-23 11 views
9

Come esercizio, ho preso questi esempi Scala e Java di Akka per portarlo a Frege. Mentre funziona bene, funziona più lentamente (11s) rispetto alla controparte di Scala (540ms).Akka con Frege che funziona più lentamente della controparte Scala

module mmhelloworld.akkatutorialfregecore.Pi where 
import mmhelloworld.akkatutorialfregecore.Akka 

data PiMessage = Calculate | 
       Work {start :: Int, nrOfElements :: Int} | 
       Result {value :: Double} | 
       PiApproximation {pi :: Double, duration :: Duration} 

data Worker = private Worker where 
    calculatePiFor :: Int -> Int -> Double 
    calculatePiFor !start !nrOfElements = loop start nrOfElements 0.0 f where 
     loop !curr !n !acc f = if n == 0 then acc 
           else loop (curr + 1) (n - 1) (f acc curr) f 
     f !acc !i = acc + (4.0 * fromInt (1 - (i `mod` 2) * 2)/fromInt (2 * i + 1)) 

    onReceive :: Mutable s UntypedActor -> PiMessage -> ST s() 
    onReceive actor Work{start=start, nrOfElements=nrOfElements} = do 
     sender <- actor.sender 
     self <- actor.getSelf 
     sender.tellSender (Result $ calculatePiFor start nrOfElements) self 

data Master = private Master { 
    nrOfWorkers :: Int, 
    nrOfMessages :: Int, 
    nrOfElements :: Int, 
    listener :: MutableIO ActorRef, 
    pi :: Double, 
    nrOfResults :: Int, 
    workerRouter :: MutableIO ActorRef, 
    start :: Long } where 

    initMaster :: Int -> Int -> Int -> MutableIO ActorRef -> MutableIO UntypedActor -> IO Master 
    initMaster nrOfWorkers nrOfMessages nrOfElements listener actor = do 
     props <- Props.forUntypedActor Worker.onReceive 
     router <- RoundRobinRouter.new nrOfWorkers 
     context <- actor.getContext 
     workerRouter <- props.withRouter router >>= (\p -> context.actorOf p "workerRouter") 
     now <- currentTimeMillis() 
     return $ Master nrOfWorkers nrOfMessages nrOfElements listener 0.0 0 workerRouter now 

    onReceive :: MutableIO UntypedActor -> Master -> PiMessage -> IO Master 
    onReceive actor master Calculate = do 
     self <- actor.getSelf 
     let tellWorker start = master.workerRouter.tellSender (work start) self 
      work start = Work (start * master.nrOfElements) master.nrOfElements 
     forM_ [0 .. master.nrOfMessages - 1] tellWorker 
     return master 
    onReceive actor master (Result newPi) = do 
     let (!newNrOfResults, !pi) = (master.nrOfResults + 1, master.pi + newPi) 
     when (newNrOfResults == master.nrOfMessages) $ do 
      self <- actor.getSelf 
      now <- currentTimeMillis() 
      duration <- Duration.create (now - master.start) TimeUnit.milliseconds 
      master.listener.tellSender (PiApproximation pi duration) self 
      actor.getContext >>= (\context -> context.stop self) 
     return master.{pi=pi, nrOfResults=newNrOfResults} 

data Listener = private Listener where 
    onReceive :: MutableIO UntypedActor -> PiMessage -> IO() 
    onReceive actor (PiApproximation pi duration) = do 
     println $ "Pi approximation: " ++ show pi 
     println $ "Calculation time: " ++ duration.toString 
     actor.getContext >>= ActorContext.system >>= ActorSystem.shutdown 

calculate nrOfWorkers nrOfElements nrOfMessages = do 
    system <- ActorSystem.create "PiSystem" 
    listener <- Props.forUntypedActor Listener.onReceive >>= flip system.actorOf "listener" 
    let constructor = Master.initMaster nrOfWorkers nrOfMessages nrOfElements listener 
     newMaster = StatefulUntypedActor.new constructor Master.onReceive 
    factory <- UntypedActorFactory.new newMaster 
    masterActor <- Props.fromUntypedFactory factory >>= flip system.actorOf "master" 
    masterActor.tell Calculate 
    getLine >> return() --Not to exit until done 

main _ = calculate 4 10000 10000 

sto facendo qualcosa di sbagliato con Akka o è qualcosa a che fare con la pigrizia in Frege per essere lento? Ad esempio, quando inizialmente avevo fold (rigida piega) al posto di loop in Worker.calculatePiFor, ci sono voluti 27 secondi.

Dipendenze:

  1. Akka definizioni native per Frege: Akka.fr
  2. Java di supporto per estendere le classi Akka dal momento che non siamo in grado di estendere una classe in Frege: Actors.java

risposta

6

Io non sono esattamente familiare con gli attori, ma supponendo che il ciclo più stretto sia effettivamente loop si potrebbe evitare di passare la funzione f come argomento.

Per uno, le applicazioni delle funzioni passate non possono sfruttare il rigore della funzione effettiva passata. Piuttosto, la generazione del codice deve assumere in modo conservativo che la funzione passata prende i suoi argomenti pigramente e restituisce un risultato pigro.

In secondo luogo, nel nostro caso si utilizza f solo una volta qui, quindi è possibile indicizzarlo. (Questo è come è fatto nel codice Scala in questo articolo si è collegato.)

guardare il codice generato per la ricorsione di coda nel seguente codice di esempio che imita la vostra:

test b c = loop 100 0 f 
    where 
     loop 0 !acc f = acc 
     loop n !acc f = loop (n-1) (acc + f (acc-1) (acc+1)) f -- tail recursion 
     f x y = 2*x + 7*y 

ci arriviamo :

// arg2$f is the accumulator 
arg$2 = arg$2f + (int)frege.runtime.Delayed.<java.lang.Integer>forced(
     f_3237.apply(PreludeBase.INum_Int._minusƒ.apply(arg$2f, 1)).apply(
      PreludeBase.INum_Int._plusƒ.apply(arg$2f, 1) 
     ).result() 
    );  

Si vede qui che si chiama f pigramente che fa sì che tutti i expressios argomento anche essere calcolati pigramente. Annota il numero di chiamate di metodo necessarie! Nel tuo caso il codice dovrebbe essere ancora qualcosa di simile:

(double)Delayed.<Double>forced(f.apply(acc).apply(curr).result()) 

Ciò significa, due chiusure sono costruiti con i valori in scatola acc e curr e poi il risultato viene calcolato, cioè la funzione f viene chiamata con gli argomenti disimballati e il risultato viene nuovamente inserito in una casella, solo per tornare nuovamente in unbox (forzato) per il ciclo successivo.

Ora confrontare il seguente, dove noi non passiamo f ma chiamiamo direttamente:

test b c = loop 100 0 
    where 
     loop 0 !acc = acc 
     loop n !acc = loop (n-1) (acc + f (acc-1) (acc+1)) 
     f x y = 2*x + 7*y 

Otteniamo:

arg$2 = arg$2f + f(arg$2f - 1, arg$2f + 1); 

Molto meglio! Infine, nel caso di cui sopra possiamo fare a meno di una chiamata di funzione a tutti:

 loop n !acc = loop (n-1) (acc + f) where 
     f = 2*x + 7*y 
     x = acc-1 
     y = acc+1 

E questo diventa:

final int y_3236 = arg$2f + 1; 
final int x_3235 = arg$2f - 1; 
... 
arg$2 = arg$2f + ((2 * x_3235) + (7 * y_3236)); 

Si prega di provare questo fuori e fateci sapere cosa succede. Il principale incremento delle prestazioni dovrebbe provenire dal non superamento dello f, mentre l'inlining sarà probabilmente fatto nella JIT.

Il costo aggiuntivo con fold è probabilmente dovuto anche alla necessità di creare un elenco prima di applicarlo.

+2

Questo è eccellente! Adesso arriva a 1,3 secondi. Ho guardato il sorgente Java generato. Ora degenera in un ciclo '' while'''. –

Problemi correlati