Benchmarking Spray Json vs. Argonaut

Posted by Derek Wyatt on January 14, 2014
Spray's Json parser vs. Argonaut's in a head-to-head battle to the death.

I’ve been coding up a JSON based REST service at work and it wasn’t performing quite the way I wanted. It’s implemented using Spray on Scala and has a Cassandra database piece that uses the Datastax library. When I profiled it (ever so simply) I noted that it was spending a lot of time in the spray-json library. Normally I wouldn’t really care, but in this case it was getting beaten out by a competing Java app running on Geronimo and, well… that just can’t stand :)

So, I took a look to see what the new Argonaut library could do. The API is very different, of course, and this post won’t show you a damn thing about it; this is all about raw speed.

The question I wanted answered was, “Will changing the JSON library increase performance?”. The answer is “yes”. But, let’s see how much…

You can see the code in question in my github repository. It’s using spray-json version 1.2.5 and Argonaut version 6.1-M2.

The JSON File

The file being parsed is 3.7k in size and looks something like this:

{
  "webapp": {
    "servlet": [   
      {
        "servletname": "cofaxCDS",
        "servletclass": "org.cofax.cds.CDSServlet",
        "initparam": {
          "configGlossaryinstallationAt": "Philadelphia, PA",
          "configGlossaryadminEmail": "ksm@pobox.com",
          "configGlossarypoweredBy": "Cofax",
          "configGlossarypoweredByIcon": "/images/cofax.gif",
          "configGlossarystaticPath": "/content/static",
...
...
    "servletmapping": {
      "cofaxCDS": "/",
      "cofaxEmail": "/cofaxutil/aemail/*",
      "cofaxAdmin": "/admin/*",
      "fileServlet": "/static/*",
      "cofaxTools": "/tools/*"
    },
    "taglib": {
      "tagliburi": "cofax.tld",
      "tagliblocation": "/WEB-INF/tlds/cofax.tld"
    }
  }
}

The Code

Pretty dead simple. All I’m doing is parsing the JSON from a string back to a string.

The spray-json version is:

package com.codeseq

import spray.json._

object Spray {
  def parseToString(jsonStr: String): String =
    jsonStr.asJson.compactPrint
}

And the Argonaut version is:

package com.codeseq

import argonaut._, Argonaut._

object Argonaut {
  def parseToString(jsonStr: String): String =
    Parse.parseOption(jsonStr).get.nospaces
}

I parsed the file 10 times for 1,000 iterations. Here are the results:

So, the upshot is that, on average (ignoring the first run of each since they were just warming up):

  • spray-json: 1234 ms
  • Argonaut: 348 ms

A little over three times faster for Argonaut. I know it’s a dead simple test, hence the ton of salt you need to take with it. I have noted that marshalling to and from case classes is also faster, but I don’t have any concrete numbers to support it.

The APIs are certainly different and I’m definitely more used to the spray-json API, which means I find the Argonaut API scary and annoying, but I’m getting there.