Search This Blog

Monday, March 1, 2010

Scala vs Clojure

At dev8d last week I was keen to go to the Scala dojo. Scala is another modern JVM language, but much more like ML than Lisp.

While there I translated the fractal tree program from Clojure to Scala.

As you can see, they're effectively the same program.

The command lines to run them are:

$ clj fractaltree.clj
$ scalac ScalaFractalTree.scala && scala ScalaFractalTree

It instantly becomes obvious that the Scala version is considerably faster.

I tried using (set! *warn-on-reflection* true), which told me that I needed to type hint the graphics objects in the clojure program, and it also says something about the proxy JPanel. I don't know how to fix that, but I don't think it's called often enough to be important.

The graphics type hints did seem to speed the Clojure version up a bit, but it's still noticeably slower than the Scala.

I'm presuming that the recursive calls to draw-tree are boxing and unboxing the parameters in the Clojure version.

Does anyone know if that's the case, and if so what can be done about it?


(import '(javax.swing JFrame JPanel )
        '(java.awt Color Graphics Graphics2D))

(defn draw-tree [ #^Graphics g2d angle x y length branch-angle depth]
  (if (> depth 0)
    (let [new-x (- x (* length (Math/sin (Math/toRadians angle))))
          new-y (- y (* length (Math/cos (Math/toRadians angle))))
          new-length (fn [] (* length (+ 0.75 (rand 0.1))))
          new-angle  (fn [op] (op angle (* branch-angle (+ 0.75 (rand)))))]
      (. g2d drawLine x y new-x new-y)
      (draw-tree g2d (new-angle +) new-x new-y (new-length) branch-angle (- depth 1))
      (draw-tree g2d (new-angle -) new-x new-y (new-length) branch-angle (- depth 1)))))

(defn render [ #^Graphics g w h ]
  (doto g
    (.setColor (Color/BLACK))
    (.fillRect 0 0 w h)
    (.setColor (Color/GREEN)))
  (let [init-length ( / (min w h) 5),
        branch-angle (* 10 (/ w h)),
        max-depth 12]
    (draw-tree  g 0.0 (/ w 2) h init-length branch-angle max-depth)))

(defn create-panel []
    "Create a panel with a customised render"

  (proxy [JPanel] []
    (paintComponent [g]
                    (proxy-super paintComponent g)
                    (render g (. this getWidth) (. this getHeight)))))


(defn run []
  (let [frame (JFrame. "Clojure Fractal Tree")
        panel (create-panel)]
    (doto frame
      (.add panel)
      (.setSize 640 400)
      (.setVisible true))))

(run)

Here's the scala version

import scala.swing._
import java.awt._
import javax.swing._

object ScalaFractalTree {
       def main(args: Array[String]){
                      val frame=new JFrame("Scala Fractal Tree")
                      val panel=new MyPanel()
                      frame add panel
                      frame setSize (640, 400)
                      frame setVisible true

                }

       }

class MyPanel extends JPanel{
  override def paintComponent(g:Graphics):Unit = {
    super.paintComponent(g)
    render(g, getWidth(), this.getHeight())
   
  }
  def render(g:Graphics, w:Int, h:Int){
      g.setColor (Color.BLACK)
      g.fillRect( 0, 0, w, h)
      g.setColor (Color.GREEN)
      val initlength=if (w<h) w/5 else h/5
      val branchangle=10*w/h
      val maxdepth=12
      drawtree( g, 0.0, w/2.0, h ,initlength, branchangle, maxdepth)
      }
      
  def drawtree(g:Graphics, angle:Double, x:Double, y:Double, length:Double, 
                             branchangle:Double, depth:Double){
     if (depth>0){
        val newx= x-length*(Math.sin(Math.toRadians( angle)))
        val newy= y-length*(Math.cos(Math.toRadians( angle)))
        val newlength1 = length*(0.75+0.1*Math.random)
        val newlength2 = length*(0.75+0.1*Math.random)
        val newangle1  = angle+branchangle*(0.75+Math.random)
        val newangle2  = angle-branchangle*(0.75+Math.random)
        g.drawLine(x.toInt, y.toInt, newx.toInt, newy.toInt)
        drawtree(g, newangle1, newx, newy, newlength1, branchangle, depth-1)
        drawtree(g, newangle2, newx, newy, newlength2, branchangle, depth-1)
        }
  }
}
                             



4 comments:

  1. I tried both and they don't seem appreciably different to me. Especially when you exclude compilation time. Do you have any specific numbers?

    ReplyDelete
  2. Clojure autoboxes everything at the function call boundary, i.e. all arguments and the return value are all boxed. So, yes, the recursive calls draw-tree will have their arguments boxed.

    IIRC nothing can be done about the boxing around function calls per se. What you can do is mark the function as inlineable or turn it into a macro in which case the function call itself gets compiled away, but I'm not sure how well that would work with a recursive function like draw-tree...

    ReplyDelete
  3. The Clojure version is much faster if you code it like the Scala version. In your Clojure version you create two closures for each draw-tree call -- new-length and new-angle which both get evaluated twice resprectively.

    In the Scala code you just create the four needed values directly. If I code the Clojure version like that as well it is a lot faster -- not surprising as it no longer has to create the closures and call them.

    ReplyDelete
  4. When I type hint all arguments to draw-tree (i.e. defn draw-tree [ #^Graphics g2d #^Double angle #^Double x #^Double y #^Double length #^Double branch-angle #^Integer depth]... ) it feels even slightly faster, but that might be wishful thinking.

    ReplyDelete

Followers