Up and Running with Clojure

For the last three years or so, Clojure has been a language that I admired from afar: the design of the language is wonderful but I’ve never really used it to build anything and haven’t looked closely at the language in a while. Recently we had a Carbon Five tech showdown between Node.js and Ruby to see which system could pump out “Hello World” as fast and as consistently as possible. Since then we’ve added a Go version that impressed us a lot.

But we’re missing a JVM-based entry which gives me a great excuse to dive into the Clojure world and learn how things work.

Getting Started

Step 1 is to create a new project for our codebase. I installed Leiningen and ran:

    lein new hellod

which creates our project structure along with placeholders for necessary files. We’ll add dependencies to our project.clj file as necessary, similar to a Gemfile in the Ruby world, including Clojure itself. Clojure is just a library for the Java VM so lein will download it like any other dependency. You should already have the JVM installed on your machine.

The Code

Now we need to implement our server in src/hellod/core.clj. Clojure doesn’t come with a simple HTTP server in its core libraries. We’ll use Aleph, which provides a simple HTTP server API on top of Java’s well-regarded Netty library.

In fact, Aleph has essentially the exact Hello World server we need to implement as an example in its README. Cut and paste for great victory!

(ns hellod.core)
(use 'lamina.core 'aleph.http)

(defn hello-world [channel request]
  (enqueue channel
    {:status 200
     :headers {"content-type" "text/html"}
     :body "</pre><h1>Hello World</h1><pre>"}))

(defn -main [& args]
  (start-http-server hello-world {:port 8083}))

Now we run the project:

    lein run

and execute ab against our server:

    ab -n 10000 -c 50 http://127.0.0.1:8083/

You can see the results for several different languages and runtimes.

We didn’t do a lot of coding in this blog post but we solved half of the problem with new environments: get something working. Now that we have a basic skeleton working, we can start learning new language features and libraries as we add functionality.

This entry was posted in Web. Bookmark the permalink.
  • Jared Carroll

    Leiningen is exactly what any new language needs to get adoption; awesome library.

  • http://rudyjahchan.com Rudy Jahchan

    I don’t know … how performant is it at calculating fibonacci numbers, the gold standard? ;-)

  • http://garytrakhman.com Gary Trakhman

    Please do some profiling so we know exactly what speed is due to clojure and what is library limitation. Benchmarks only benchmark the weakest link. I think saying ‘clojure gets these numbers’ is misleading otherwise. The closest to apples/apples would be with jruby, but it’s likely that jruby’s runtime itself is not the limiter in its benchmark as well.

  • http://garytrakhman.com Gary Trakhman

    try straight up ring and jetty for a boost

  • Mike Perham

    Gary, yeah, I’m not a Clojure pro – I was explicit about that in the lead-in. The HelloD code is open and we’d love to have a Clojure expert tune it for better performance. Just send us a pull request!

    • http://garytrakhman.com Gary Trakhman

      added ring and jetty on port 8084, you’ve got a pull request

  • Daniel Fitzpatrick

    You could switch to cake to simplify the installation instructions ie “Install Leiningen from https://github.com/technomancy/leiningen” becomes “gem install cake”.

    I’m also curious as to how the java.net standard libs compare.

  • James Fisher

    Aren’t those `pre` tags in the wrong order?

  • Anonymous

    Simply following your advice results in:
    “No :main namespace specified in project.clj.”
    after “lein run”

  • cameron

    As someone who is just getting started with Clojure, this is a great post on the tools that exist around the language to support development. Thanks!

  • http://twitter.com/regadas Filipe Regadas

    Really great post. Awesome introduction to some of the clojure tools. IMHO your “hello world” showdown misses one important piece … a more “scala”-ish approach. I advise you to try unfiltered + netty. Let us know what you think ;)

  • http://twitter.com/ninjudd ninjudd

    Mike, great post!

    These results didn’t look quite right to us, so we re-ran the benchmark ourselves for Ruby and Clojure. Here’s our results:

    https://gist.github.com/1305029

    We ended up running the test multiple times. The first pass looks pretty similar to your results, though a bit faster overall.

    In later passes, Clojure/Ring improved quite a bit, even beating Ruby/EventMachine. It’s likely that this speedup is from warming the JIT.

    The slow speed of Clojure/Aleph was also surprising to me, given past blog posts about it (http://dosync.posterous.com/22397098). However, that was a while ago, and Aleph is in active development, so there may have been performance regressions since then. A quick `cake check` in clj-aleph shows quite a few reflection warnings that may be contributing to the slowness.

    • http://twitter.com/mwynholds Michael Wynholds

      Ninjudd-

      I have been doing the benchmarking. I re-ran the Clojure/Ring benchmarks 10 times trying to let the JIT warm up, but continue to get the same results.

      I am using a EC2 High CPU Extra Large, which has 8 cores, but I think each core is relatively slow compared to what is sitting inside our laptops. Watching the process (just with top), the CPU never goes above 10%. So it’s clearly not taking advantage of multiple cores. That probably explains why my numbers are larger in a absolute sense than yours.

      As for the JIT… what JVM are you using? I am guessing you are using Sun’s. I am using OpenJDK, which probably has a crappier JIT.

      I will try to re-run the Java-based platforms with OpenJDK and Sun’s JDK and post the results.

      • http://twitter.com/ninjudd ninjudd

        Michael-

        We were using OpenJDK too, but I’d still be curious to see your comparison of Sun’s JDK and OpenJDK. Here is the output from ‘java -version’

        java version “1.6.0_22″
        OpenJDK Runtime Environment (IcedTea6 1.10.2) (6b22-1.10.2-0ubuntu1~11.04.1)
        OpenJDK 64-Bit Server VM (build 20.0-b11, mixed mode)

  • Chris Hagan

    If anyone’s looking to replicate this, and is new to Clojure, here are a few extra details to these steps:

    lein new hellod
    cd hellod

    (defproject hellod “1.0.0-SNAPSHOT”
    :main hellod.core
    :description “FIXME: write description”
    :dependencies [
    [org.clojure/clojure "1.2.1"]
    [aleph "0.2.0"]])

    Mostly the point of detail here is to add the :main directive to the project.clj so that it knows where it’s going to find its entry point.

    Great instructions, Mike! Very useful adjunct to the expert-level documentation around Aleph and Clojure already available on the web.