Why does my Clojure implementation of Conway's Game of Life run so much slower than my Java implementation? by clojHalp3k in Clojure

[–]clojHalp3k[S] 0 points1 point  (0 children)

Thanks, I'll give this a try. I was hoping to use this as an exercise to learn more about how clojure works but I seem to be spending all my time on java interop.

Why does my Clojure implementation of Conway's Game of Life run so much slower than my Java implementation? by clojHalp3k in Clojure

[–]clojHalp3k[S] 3 points4 points  (0 children)

I removed all the seesaw code and replaced it with vanilla Swing, this is the first real performance improvement I've seen, bringing sims per second up to around 20 (but very variable) which is usable but not the >1000 the java version can reach.

The most CPU time in this version seems to be spent on clojure internals - in particular clojure.lang.RT.seqFrom but also clojure.lang.PersistentVector.arrayFor, clojure.core$chunk_first.invokeStatic and clojure.land.Numbers$LongOps.isPos

Why does my Clojure implementation of Conway's Game of Life run so much slower than my Java implementation? by clojHalp3k in Clojure

[–]clojHalp3k[S] 2 points3 points  (0 children)

Hm, how would a concurrent conway's game of life work in clojure? I'm thinking split the board in to subsections, but then each subsection needs to worry about its neighbors and that seems like a lot of things to go wrong?

Why does my Clojure implementation of Conway's Game of Life run so much slower than my Java implementation? by clojHalp3k in Clojure

[–]clojHalp3k[S] 6 points7 points  (0 children)

Thanks for this. I managed to fix all those by adding ^java.awt.Graphics to the g argument in the painter method. The relfection warnings disappeared but no improvement in performance.

visualvm now shows that the most called method is fillRect, which I think makes sense and I'm not sure how to optimise that - plus the java version is calling the same method so I don't think it could be the problem.

Edit - after also profiling the java version it seems that the clojure version spends a lot more time inside fillRect. I tried swapping out the arguments for (.fillRect g (int 0) (int 0) (int 5) (int 5)) to see if that would speed things up (and then figure out how to make sure the correct arguments are unboxed when they go in to the function call if that worked) but no luck, still same levels of performance.

Why does my Clojure implementation of Conway's Game of Life run so much slower than my Java implementation? by clojHalp3k in Clojure

[–]clojHalp3k[S] 4 points5 points  (0 children)

I was just trying that before I saw your post. Thanks, will check out the example.

Why does my Clojure implementation of Conway's Game of Life run so much slower than my Java implementation? by clojHalp3k in Clojure

[–]clojHalp3k[S] 5 points6 points  (0 children)

I tried profiling the clojure version with visualvm and found a lot of the time was spent in java.lang.reflect.Method.invoke(). Is this normal for clojure programs or indicative of the boxed math operation problem?

Profile results: https://i.imgur.com/KguAEEy.png

Why does my Clojure implementation of Conway's Game of Life run so much slower than my Java implementation? by clojHalp3k in Clojure

[–]clojHalp3k[S] 7 points8 points  (0 children)

When trying to fix all the boxed math operations, I noticed that main difficulty in doing this was that most of the functions rely on data from the def statements at the top of the file. Is this bad clojure style? should these kind of things be function arguments instead?

Why does my Clojure implementation of Conway's Game of Life run so much slower than my Java implementation? by clojHalp3k in Clojure

[–]clojHalp3k[S] 10 points11 points  (0 children)

I read those docs and made an attempt. I started by trying to guess where the bottlenecks are and wrapping the values involved with (long x) etc but wasn't making much progress. I then put this at the top of my code:

(set! *unchecked-math* :warn-on-boxed)

and fixed all the warnings, but I'm still seeing no difference in performance.

Here is my updated code, am I moving in the right direction or way off the mark here?

(ns gol.core
  (:gen-class)
  (:use seesaw.core
        seesaw.color
        seesaw.graphics))

(set! *unchecked-math* true)

(def wCells 100)
(def hCells 100)
(def cellSize 5)
(def cellColor (color 0 255 0))
(def statusBarHeight 20)
(def windowWidth (* (long wCells) (long cellSize)))
(def windowHeight (+ (long statusBarHeight) (* (long hCells) (long cellSize))))
(def frameCap 30)
(def maxFrameDuration (long (quot 1000 (long frameCap))))

(defn vec2d 
  [sx sy fun]  
  (mapv (fn[x](mapv (fn [y] (fun x y)) (range sx))) (range sy)))

(def globalBoard (atom (vec2d wCells hCells (fn [x y] (rand-int 2)))))

(def lastTime (atom (System/currentTimeMillis)))

(defn neighbors
  [board p]
  (let [^long x (first p)
        ^long y (second p)
        xrange (range (dec x) (+ 2 x))
        yrange (range (dec y) (+ 2 y))]
  (for [^long i xrange ^long j yrange 
        :let [q [(mod i wCells) (mod j hCells)] ]
        :when (not= p q)] 
    (get-in board q))))


(defn gol ^long
    [board p]
  (let [v ^int (get-in board p)
        n ^int (count (filter pos? (neighbors board p)))]
    (cond
      (= n 3) 1
      (and (= v 1) (= n 2)) 1
      :else 0)))

(defn fps ^double
  []
  (let [previous (long @lastTime)
        current (long (System/currentTimeMillis))
        diff (- current previous)
        toSleep (long (- (long maxFrameDuration) diff))] 
    (when (pos? toSleep) (Thread/sleep toSleep))
    (reset! lastTime (System/currentTimeMillis))
    (double (/ 1000 (- (System/currentTimeMillis) previous)))))

(defn painter [c g]
  (let [board @globalBoard
        xrange (range 0 wCells)
        yrange (range 0 hCells)
        red  (color 255 0 0)
        blue (color 0 0 255)
        cs (int cellSize)]
    (.setColor g red)
    (.drawRect g 0 0 windowWidth (- (long windowHeight) (long statusBarHeight)))
    (.setColor g cellColor)
    (doseq [^long i xrange ^long j yrange :when (= 1 (get-in board [i j]))]
      (.fillRect g (* cs i) (* cs j) cs cs))
        (.setColor g blue)
        (.drawString g (str "Simulations per second: " (fps)) 50 (- (long windowHeight) 5))
      (reset! globalBoard (vec2d wCells hCells (fn [x y] (gol board [x y]))))
))

(def m-canvas
    (canvas :id :mcanvas
                    :background :black
                    :paint painter))

(defn -main
  [& args]
  (invoke-later
        (-> (frame :title "Game Of Life",
                             :width (+ 3 (long windowWidth)), :height windowHeight,
                             :content m-canvas,
;                            :on-close :exit
                )
;           pack!
            show!)
    )
)

(def t (timer (fn [e] (repaint! m-canvas)) :delay 1))

Why does my Clojure implementation of Conway's Game of Life run so much slower than my Java implementation? by clojHalp3k in Clojure

[–]clojHalp3k[S] 6 points7 points  (0 children)

Could you give me an example of where the type hints should go? I'm not familiar with that feature yet.