2016-02-15 15 views
5

Um zu verstehen, core.async zu versuchen, habe ich versucht, unsuccesfully den "Skynet 1 Million-Micro", zu implementieren, die ist:Wie implementiert man das Skynet 1m microbenchmark mit core.async?

Erzeugt einen Schauspieler (goroutine, was auch immer), die 10 neue Akteure laicht jeder von ihnen spawnt 10 weitere Schauspieler, etc., bis eine Million Schauspieler auf der letzten Ebene erstellt werden. Dann gibt jeder von ihnen seine Ordinalzahl (von 0 bis 999999) zurück, die auf der vorherigen Ebene summiert und stromaufwärts zurückgesendet werden, bis der Hauptakteur erreicht wird. (Die Antwort sollte 4999999500000 sein).

Es gibt Implementierung in vielen Sprachen hier:

https://github.com/atemerev/skynet

Hier ist mein total kaputt Versuch:

(defn skynet [chan num size div] 
    (if (= 1 size) 
    (>! chan num) 
    (>! chan (reduce + (let [rc (async/chan) 
          n (/ size div)] 
          (doall (for [i [0 div]] 
            (skynet rc (+ num (* i n)) n div)) 
           (for [i [0 div]] (<! rc)))))))) 

Und ich habe versucht, alles aus dem Inneren eines Go-Block rufen an die REPL:

(time (go (<!! (skynet (async/chan) 0 1000000 10)))) 

Ich bin wahrscheinlich sehr verwirrt über viele Dinge in Bezug auf core.async (und faule Auswertung zu).

Wie soll ich dieses Problem lösen und warum?

Antwort

7

Es gibt some limitations auf was core.async kann tun, so dass Sie die map oder for Funktionen nicht verwenden können.

Ihre Implementierung ist ziemlich nah an der richtigen. Einige Punkte:

  1. go == ein Prozess, so dass Sie nur einen Prozess zu schaffen, nicht 1m
  2. <!! verwendet außen zu gehen blockieren
  3. <! ist im Inneren verwendet werden gehen Blöcke
  4. Sie verwenden for falsch
  5. doall akzeptiert nur einen Parameter

Eine Arbeits Implementierung, die wahrscheinlich verbessert werden kann:

(defn skynet [parent num size div] 
    (go ;; We create a new process each time skynet is called 
    (if (= 1 size) 
     (>! parent num) 
     (let [self (chan) 
      new-size (/ size div)] 
     (dotimes [i div] ;; dotimes is more explicit for side effects 
      (skynet self (+ num (* i new-size)) new-size div)) 
    (loop [i div ;; Manual reduce 
      t 0] 
     (if (zero? i) 
     (>! parent t) 
     (recur (dec i) 
       (+ t (<! self))))))))) 

Und es zu nennen:

(time 
    (do 
    (def result (chan)) 
    (def x (skynet result 0 1000000 10)) 
    (<!! result))) 
+0

Vielen Dank! Eine Menge Clojure zu studieren, aber das wird mir definitiv sehr helfen, nicht nur deine Version, sondern auch deine Kommentare darüber, was in meinem Code falsch ist. Seltsamerweise sind einige * "a /" * in deiner letzten Codezeile erschienen, ich habe sie bearbeitet :) –

+0

Natürlich langsamer als nur mit einem Reducer, aber das ist der Sinn des Benchmarks. Ich habe versucht, eine Version der 'skynet'-Funktion zu schreiben, die einen Kanal zurückgeben würde, der das Ergebnis enthält (anstatt eines als Argument zu verwenden) und async/reduce anstelle eines manuellen reduce verwendet und keinen Weg gefunden hat, dies zu tun. Ich wäre an einer solchen Version interessiert. Wie auch immer, Kudos! – nha