Note

EDIT 2012-08-08: Added benchmarks in Firefox in addition to Chrome

In the last post, I outlined some of the architectural difficulties bringing matplotlib's interactivity to the browser. In short, there's a big chunk of code that lies between building the tree of artists and rendering them to the screen that needs to run either in the Python interpreter, as it does now, or inside of the web browser to support interactive web applications. It would be great to avoid having two code bases, one in Python and one in Javascript, that would need to be kept in sync. Writing code for both contexts from a single code base may turn out to be a pipe dream, but bear with me as I explore tools that might help.

Also, when trying to grok the discussion here and understanding the architectural challenges of matplotlib, it may be helpful to read the matplotlib chapter by John Hunter and yours truly from Architecture of Open Source Applications, Volume II.

Tools

There are a few interesting projects that help bridge the gap between Python and Javascript.

PyJs

PyJs (formerly called Pyjamas) is a Python-to-Javascript converter. It also includes an environment much like Google Web Toolkits for developing rich client-side applications in Python, but those features are probably not useful here.

Skulpt

Skulpt is a Python interpreter written in Javascript. It can compile and run Python code entirely within the web browser. In terms of language features, it doesn't seem as mature as PyJs, but the fact that it has no dependencies other than a couple of Javascript files may be an advantage in terms of deployment. An obvious shortcoming of both Skulpt and PyJs is the lack of support for Numpy -- none of the existing matplotlib Python code, which depends so heavily on Numpy, would work in that context.

PyV8

Unlike the other two, which allow Python to run in the browser, PyV8 allows Javascript to run inside of the Python interpreter. It is a wrapper around Google's open source V8 Javascript engine and allows sharing objects between Python and Javascript somewhat transparently. If the drawing code were to be rewritten in Javascript, it could then, theoretically, be used both from Python on the desktop and inside the web browser.

Playing around

As a first pass playing with these tools, I've created a new project on github py-js-blending-experiments. I've started by writing a very simple benchmark that does a simple 2D affine transformation, in pure Python, Python with Numpy, Javascript and C. This test, while a real-world bottleneck from the real-world matplotlib, is probably too simple to read too much into the results. A real test would involve classes with complex interactions between them, to show how the same flexible system of transformations, tickers, formatters etc. would work, and would take into account the penalty of stepping over the gap between Python and Javascript. But all that will have to wait for a future blog post.

The benchmarks

The benchmarks compare a number of different possible approaches.

  • Pure Python: This is just a simple pure Python implementation. transform.py
  • Pure Javascript: A hand-written JavaScript implementation. transform.js
  • Numpy: An implementation using vectorized Numpy operations. transform_numpy.py
  • C extension: A hand-written C extension. transform.c
  • Skulpt: Taking the pure Python implementation above, but running through Skulpt to get it to run inside of the browser.
  • PyJs: Compiling the pure Python implementation above to Javascipt using PyJs, and then running the result in the browser.
  • PyV8: Running the hand-written Javascript implementation above inside of PyV8.

Results

The following results are on a quad-core Intel Core i5-2520M CPU @ 2.50GHz running Fedora Linux 17. Python 2.7.3, Numpy 1.6.1, Google Chrome 21.0.1180.57 beta and Firefox 14.0.1 were used. The benchmark is performing a 2D affine transformation on 128,000 points. Note that the timings in the web browser are quite variable. I've included the average and independent results of 5 runs.

=========================== ================== ==================================
Benchmark                   avg. time (in ms)  times
=========================== ================== ==================================
Pure Python                 94                 95, 96, 93, 96, 92
Pure Javascript Chrome      40                 41, 29, 59, 33, 42
Pure Javascript Firefox     15                 8, 7, 25, 20, 16
Numpy                       6                  7, 6, 6, 6, 6
C                           2                  2, 2, 2, 2, 2
Skulpt Chrome               686                700, 691, 676, 691, 676
Skulpt Firefox              1052               1027, 1052, 1062, 1060, 1061
PyJs Chrome                 2197               2156, 2218, 2176, 2187, 2251
PyJs Firefox                658                644, 687, 630, 680, 674
PyV8                        38                 38, 38, 38, 37, 37
=========================== ================== ==================================

Conclusion

So what can we conclude? Remember what I said about not reading too much into these results? Well, I'm going to do it anyway with an enormous caveat.

There is considerable overhead when trying to shoehorn Python into the browser (comparing Skulpt and PyJs to Pure Javascript). I personally am surprised by how much more successful the Python interpreter approach is vs. the Python to Javascript conversion approach, though that may be due to the relative incompleteness of Skulpt. (EDIT: Though the Firefox results tell the opposite story). It's pretty clear where the overhead of PyJs comes from. The line:

X = a*x + c*y + e

converts to:

X = $p['__op_add']($add3=$p['__op_add']($add1=(typeof ($mul1=a)==typeof ($mul2=x) && typeof $mul1=='number'?
    $mul1*$mul2:
    $p['op_mul']($mul1,$mul2)),$add2=(typeof ($mul3=c)==typeof ($mul4=y) && typeof $mul3=='number'?
    $mul3*$mul4:
    $p['op_mul']($mul3,$mul4))),$add4=e);

You can see how basic numeric operators in Python don't translate directly to those in Javascript, so it's forced to do something a whole lot more dynamic, including typechecking within every loop iteration. I pity the fool Javascript engine that tries to optimize that.

Not surprisingly, the PyV8 engine performs comparably to the V8 engine embedded in Google Chrome, which also beats pure Python by at least a factor of 2. We could do rather well implementing this core in Javascript.

Numpy and C extensions, of course, beat everything handily for this very numerically-biased benchmark.

Where does that leave us? Who knows... Interesting ride, though. Stay tuned and leave comments... There's more to hack away at.


Comments

comments powered by Disqus