Faster JSON Performance

139 views
Skip to first unread message

David Novakovic

unread,
May 13, 2010, 10:00:41 PM5/13/10
to Tornado Web Server
Hey all,

Just thought I'd share this with you.

We run a web twitter client. We deal with up to 2.5 million json
requests a day.

We moved to jspickle for json decoding/encoding. Just monkypatched
tornado/escape.py to use the relevant functions in jspickle.

See just before "Thu 12" the image below for the difference in CPU
load:

http://twitpic.com/1nj0q2

Just thought I'd let people know. :)

D

Matthew Ferguson

unread,
May 14, 2010, 4:05:46 AM5/14/10
to python-...@googlegroups.com
David,

Could you give us some more information on the configuration before you switched to jspickle? Were you using the C-compiled version of simplejson?

David P. Novakovic

unread,
May 14, 2010, 6:49:23 AM5/14/10
to python-...@googlegroups.com
Indeed we were using the compiled version of simplejson (the _json extension). I checked before installing jspickle, mainly because I was dubious of the performance gain we would see!

After using jspickle and fixing our nginx config (moved back to 1 worker from 4) we are seeing the site running a lot smoother. :)

D

elij

unread,
May 14, 2010, 10:03:50 PM5/14/10
to python-...@googlegroups.com
looks like jspickle is based on cjson, which is quite fast.
you might also look at python yajl, as yajl is a pretty good c library for json.

David P. Novakovic

unread,
May 15, 2010, 8:27:50 PM5/15/10
to python-...@googlegroups.com
My overly simple 'integration' looks like this... see the last 3 lines. :P

try:
    import json
    assert hasattr(json, "loads") and hasattr(json, "dumps")
    _json_decode = lambda s: json.loads(s)
    _json_encode = lambda v: json.dumps(v)
except:
    try:
        import simplejson
        _json_decode = lambda s: simplejson.loads(_unicode(s))
        _json_encode = lambda v: simplejson.dumps(v)
    except ImportError:
        try:
            # For Google AppEngine
            from django.utils import simplejson
            _json_decode = lambda s: simplejson.loads(_unicode(s))
            _json_encode = lambda v: simplejson.dumps(v)
        except ImportError:
            raise Exception("A JSON parser is required, e.g., simplejson at "
                            "http://pypi.python.org/pypi/simplejson/")

import jspickle
_json_decode = jspickle.decode
_json_encode = jspickle.encode

Cliff Wells

unread,
Sep 5, 2010, 11:56:24 PM9/5/10
to python-...@googlegroups.com
On Sun, 2010-05-16 at 10:27 +1000, David P. Novakovic wrote:

> try:
> import json
> assert hasattr(json, "loads") and hasattr(json, "dumps")
> _json_decode = lambda s: json.loads(s)
> _json_encode = lambda v: json.dumps(v)

This should probably be:

_json_decode = json.loads
_json_encode = json.dumps

The lambda amounts to an additional function call with no visible
benefit. Given the frequency of these calls it's probably a measurable
amount of overhead.

Regards,
Cliff


David P. Novakovic

unread,
Sep 5, 2010, 11:59:32 PM9/5/10
to python-...@googlegroups.com
Cliff,

My hack is under the block you saw there. :)

import jspickle
_json_decode = jspickle.decode
_json_encode = jspickle.encode

D

Cliff Wells

unread,
Sep 6, 2010, 12:21:30 AM9/6/10
to python-...@googlegroups.com
On Mon, 2010-09-06 at 13:59 +1000, David P. Novakovic wrote:
> Cliff,
>
> My hack is under the block you saw there. :)
>
> import jspickle
> _json_decode = jspickle.decode
> _json_encode = jspickle.encode

You could also achieve this without hacking the Tornado source by using:

import jspickle
import tornado.escape
tornado.escape._json_decode = jspickle.decode
tornado.escape._json_encode = jspickle.encode

Modules are singletons, so this change is global.

Regards,
Cliff

Cliff Wells

unread,
Sep 6, 2010, 1:40:41 AM9/6/10
to python-...@googlegroups.com
On Sun, 2010-09-05 at 21:21 -0700, Cliff Wells wrote:
> On Mon, 2010-09-06 at 13:59 +1000, David P. Novakovic wrote:
> > Cliff,
> >
> > My hack is under the block you saw there. :)
> >
> > import jspickle
> > _json_decode = jspickle.decode
> > _json_encode = jspickle.encode
>
> You could also achieve this without hacking the Tornado source by using:
>
> import jspickle
> import tornado.escape
> tornado.escape._json_decode = jspickle.decode
> tornado.escape._json_encode = jspickle.encode

Actually, it turns out that jspickle also escapes forward slashes:

>>> jspickle.encode("</")
'"<\\/"'

so there's no need for the provided tornado functions at all. That
makes this preferable:

tornado.escape.json_decode = jspickle.decode
tornado.escape.json_encode = jspickle.encode

This eliminates an extra function call for each.

Regards,
Cliff


David P. Novakovic

unread,
Sep 6, 2010, 1:42:22 AM9/6/10
to python-...@googlegroups.com
Nice pickup.

I'll adjust our code. :)

Ben Darnell

unread,
Sep 6, 2010, 3:00:00 AM9/6/10
to python-...@googlegroups.com
On Sun, Sep 5, 2010 at 10:40 PM, Cliff Wells <cl...@develix.com> wrote:
> Actually, it turns out that jspickle also escapes forward slashes:
>
>>>> jspickle.encode("</")
> '"<\\/"'

This kind of subtle behavioral difference is exactly why
tornado.escape doesn't try to silently switch between different JSON
libraries depending on what third-party packages are installed. There
aren't many places where Tornado JSON-encodes things for you (it's
mostly used in the auth module which isn't performance-critical). If
you want to use a different JSON library for performance, I recommend
simply calling it instead of tornado.escape.json_{en,de}code (and
maybe override write() in your RequestHandler subclass to do the
dict-to-json conversion with this library).

-Ben

Reply all
Reply to author
Forward
0 new messages