2013-06-23 10 views
9

Jako ćwiczenie, wziąłem te Scala i Java przykłady Akka do portu do Frege. Chociaż działa dobrze, działa wolniej (11s) niż odpowiednik Scala (540ms).Akka z Fregą działa wolniej niż odpowiednik 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 

robię coś nie tak z Akka czy to ma coś wspólnego z lenistwem w Fregego za to powoli? Na przykład, kiedy początkowo miałem fold (ścisłe fałdowanie) w miejsce loop w Worker.calculatePiFor, zajęło to 27 sekund.

zależności:

  1. Akka rodzime definicje dla Fregego: Akka.fr
  2. Java pomocnik przedłużyć zajęcia Akka ponieważ nie możemy rozszerzyć klasę w Frege: Actors.java

Odpowiedz

6

nie jestem dokładnie zaznajomiony z Aktorami, ale zakładając, że najciaśniejsza pętla jest rzeczywiście loop, możesz uniknąć podania funkcji f jako argumentu.

Po pierwsze, aplikacje przekazanych funkcji nie mogą wykorzystywać ścisłości rzeczywistej przekazywanej funkcji. Zamiast tego generowanie kodu musi zakładać konserwatywnie, że przekazana funkcja leniwie przyjmuje swoje argumenty i zwraca leniwy wynik.

Po drugie, w naszym przypadku używamy f naprawdę tylko raz tutaj, więc można go wstawić. (Jak to jest zrobione w kodzie Scala w artykule ty powiązane.)

spojrzeć na kod wygenerowany dla rekursji ogonowej w następujący przykładowy kod, który naśladuje Ciebie:

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 

My tam dostać :

// 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() 
    );  

tu zobaczyć, że f nazywa się leniwie, co powoduje wszystkie expressios argument być również obliczony leniwie. Zwróć uwagę na liczbę wywołań metod, która jest wymagana! W twoim przypadku kod powinien być jeszcze coś:

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

Oznacza to, dwa zamknięcia są budować z pudełkowej wartości ACC i Curr a następnie wynik jest obliczany, czyli funkcji f jest wywoływana z odpakowanych argumentów , a wynik zostanie ponownie zapakowany, po prostu ponownie rozpakowany (wymuszony) do następnej pętli.

Teraz porównać następujące, gdzie po prostu nie przechodzą f ale nazywają go bezpośrednio:

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 

Dostajemy:

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

wiele lepiej! Wreszcie, w przypadku powyżej możemy zrobić bez wywołania funkcji w ogóle:

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

I to dostaje:

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

Spróbuj tego i daj nam znać, co się dzieje. Główne zwiększenie wydajności powinno wynikać z nieprzedstawienia f, podczas gdy w przypadku JIT prawdopodobnie będzie to możliwe.

Dodatkowy koszt z fold wynika prawdopodobnie z faktu, że przed złożeniem wniosku trzeba było utworzyć listę.

+2

To jest doskonałe! Teraz sprowadza się do 1,3 s. Spojrzałem na wygenerowane źródło Java. Teraz degeneruje się do pętli '' 'while'''. –

Powiązane problemy