Monday, November 25, 2013

Pystone - PythonJS vs PyPy


The following Pystone benchmarks were performed with the default 50,000 iterations, the results are the average of five runs. PythonJS is tested in four different modes with greater levels of optimizations. Test machine is: dual core 2.4GHZ, 4GB RAM, LinuxMint with Google-Chrome v31.

PythonJS vs Brython and Empythoned

Above: Brython, Empythoned, PythonJS in default mode, and PythonJS with inlined optimization. PythonJS outperforms other browser based Python's: Empythoned (3,320 pystones) and Brython (425 pystones). PythonJS in default mode gets 8,150 pystones per-second. See Brython's JavaScript output here

PythonJS vs PyPy

Python2.7 reaches an average of 112,257 Pystones per-second. Pythonium (another Python to JavaScript translator) in its fastest veloce mode reaches an average of pystones 291,480 per-second. PythonJS in javascript mode, reaches an average of 850,120 Pystones per-second. PyPy-1.9 is the winner with an average score of 1,813,461 Pystones per-second.


Brython PythonJS Integration

I am also working on integrating Brython's tokenizer and parser into PythonJS so that PythonJS can be fully self hosted. The file in Brython's source code is py2js.js. It transforms Python code into its own abstract syntax tree that is very similar to the standard Python AST. I am making code to walk this tree and convert it into a standard AST that is required by the translator in PythonJS. See these files, and contact me if you would like to help.

Sunday, November 10, 2013

Brython vs PythonJS


If you have not heard of Brython, it is one of the most popular in-browser Python implementations. From the top of Brython's documentation: "Brython's goal is to replace Javascript with Python, as the scripting language for web browsers." Brython is a long way from reaching this goal, because as we will see below, it has serious performance issues. Brython is slow because of its conformant implementation of the Python standard, which is very dynamic and is generally hard to map to JavaScript.

The following tests compare the performance of Brython 1.1, Python 2.7, PyPy 1.9 and PythonJS 0.8.3. Note that PyPy tests were done with a warmed up JIT. These tests were performed with N=100,000 on a dual core 2.4GHZ machine with 4GB of ram. (test scores below are given in seconds)

Test1 - for loop:

 a = 0
 for x in range( N ):
  a += 1
  • Brython: 6.8249
  • Python: 0.0064
  • PyPy: 0.00267
  • PythonJS: 0.0009

In a simple for loop over a range, with a single variable incremented: Brython is 1,000 times slower than Python, and 7,500 times slower the PythonJS. Lets examine the output of Brython to see why it is so slow.

Brython Output

a=$globals["a"]=Number(0)
var $iter61=iter( range.__call__(N) )
var $no_break61=true;
while(true){
    try{
        x=$globals["x"]=$iter61.__next__()
    }
    catch($err){
      if($err.__name__=="StopIteration"){break}
      else{throw($err)}
    }
    document.$line_info=[21,"__main__"];None;
    $temp=Number(1)
    if(!hasattr(a,"__iadd__")){
        a=$globals["a"]=a.__add__($temp)
    }
    else{
        a=a.__iadd__($temp)
    }
}

As we can see above, Brython generates alot of code for such a simple for loop. Instead of using JavaScript's native number type, Brython has its own Number object. It has a try/catch to catch the StopIteration exception, and also it testing each loop if the number has a method __iadd__, and if not it falls back to using __add__. Lets take a look at how PythonJS translates the same code, and why it is 7,500 times faster in this case.

PythonJS Output

a = 0;
var x;
x = 0;
while(x < N) {
 a += 1
 x += 1
}

The PythonJS compiler knows that a for loop over a range is actually just a simple loop that can be translated to fast JavaScript.

Test2 - while loop:

 a = 0
 i = 0
 while i < N:
  a += 1
  i += 1
  • Brython: 13.5709
  • Python: 0.01008
  • PyPy: 0.00144
  • PythonJS: 0.001

Things get worse for Brython when the loop gets more complicated, this tests increments two values. In this test Brython is 1,300 times slower than Python.

Test3 - calling simple function:

 a = 0
 for x in range(N):
  a += no_args()
  • Brython: 7.717
  • Python: 0.0137
  • PyPy: 0.0025169
  • PythonJS: 0.0019

Test4 - function call with normal arguments:

 a = 0
 for x in range(N):
  a += no_kwargs(1,2,3)
  • Brython: 15.3209
  • Python: 0.0250
  • PyPy: 0.00279
  • PythonJS: 0.3729
  • PythonJS + with fastdef: 0.0039

In this test PythonJS is 14 times slower than Python, this is because by default PythonJS functions that take arguments, have logic to check those arguments at runtime, and to also check if the function was called from JavaScript and if so - adapt the arguments. This can be bypassed using the @fastdef function decorator, or fastdef with statement `with fastdef:`.

In the above test, PythonJS with fastdef is 6.4 times faster than Python. Note that you can still call fastdef function from external JavaScript, you only need to pack arguments into an array as the first argument, and pack keyword args into an Object as the second argument. Example: func( [x,y,z], {a:1, b:2, c:3} )

Test5 - function call with keyword arguments:

 a = 0
 for x in range(N):
  a += call_kwargs(1,2,3, x=1, y=2, z=3)
  • Brython: 14.75
  • Python: 0.0370
  • PyPy: 0.00417
  • PythonJS: 0.631
  • PythonJS + with fastdef: 0.006

Conclusion

Brython is slow and heavy, written almost entirely in JavaScript, and closely follows the Python language standards. Its runtime brython.js is 227KB. Its tokenizer and compiler, py2js.js is 3,500 lines of JavaScript code. It lacks static type analysis and optimizations. Brython is able to translate Python code to JavaScript on the client side. Its performance is much slower than Python.

PythonJS is fast and light weight, written in pure Python, and tries to balance performance concerns with the Python language standards (read more here). Its runtime pythonjs.js is 98KB. The PythonJS translator is 1,900 lines of Python code. The translator includes static type analysis, and optimizes operator overloading and attribute access. It lacks a tokenizer and currently can only translate code on the server side using Python2 as its host. Its performance can surpass Python and approach PyPy in some cases, because it tries to output JavaScript JIT friendly code.

PythonJS 0.8.3 Released

Today I have released PythonJS 0.8.3, this includes the new fastdef and fast for loop optimizations. The performance test above is also included under tests/test_performance.html. The next release of PythonJS will feature integration with NodeJS.

Friday, November 8, 2013

Pixi.js and Blockly Animation System


I am currently working on a way to drive animations in Pixi.js using Tween.js and GoogleBlockly. The new SpriteController Block directly parses the conditional blocks it contains, if the selected Event is True, then the animation will play. In this example the Bee moves left and right when the left and right keys are pressed.

Next I will be working on improving the event system by adding more event types, branching logic, relative animation, and variables.

Wednesday, November 6, 2013

Pixi.js and GoogleBlockly


Pixi.js

Pixi.js is a fast 2D rendering engine that is optimized for WebGL, and is able to use HTML5 Canvas as a fallback. Using WebGL Pixi is able to render high resolution graphics very quickly. The Pixi API is well designed, and easily wrapped in PythonJS, see my bindings here.

Google Blockly Integration

The officially documented way to integrate Google Blockly with an external API is using Blockly.addChangeListener( my_callback ), your callback will be called each time the UI is updated. Your callback can then call Blockly.JavaScript.workspaceToCode() and then use JavaScript's eval to execute the string that it returns. This only allows for weak integration, here are the following problems:

  1. Your callback will be called for every single UI event, like moving blocks, or typing in an input field. This is slow and makes it very hard to catch just the events you are interested in.
  2. You have no way to send signals back to the blocks that generated the code. This makes it impossible to update fields on Blockly's blocks, like number fields. For example, your custom block creates a Sprite, and then in the game the sprite is moved, now your code will need to update the position field on the block in Blockly's workspace, but the Sprite has no reference to the block to do so.
  3. Each time your callback is triggered, you might have to tear down and reinitialize alot of state.
  4. This is generally a bad fit for an Actor model, where entities carry out their own behaviors, and interact with their environment using dynamic rules.

The above problems show us that the official way to integrate Blockly is pretty much useless with a dynamic system that requires bi-directional updates. The workaround to these problems requires us to modify Blockly's prototypes and functions so that blocks can be created from an external API, and UI events can be caught directly for each field. My solution is implemented in the binding layer between Blockly and PythonJS. Blockly's source code can remain intact - because prototypes and functions can be changed at runtime in JavaScript using special syntax in PythonJS. You can see my binding here.

Wrapper Decorators

One of my goals when starting the integration between Google Blockly and Pixi was to write as little wrapper code as possible, but not so little that it was unclear how things are tied together. To achive this I implemented two special class decorators: @pythonjs.init_callbacks and @pythonjs.property_callbacks. These are different from normal Python class decorators because they inject some code and attributes into the class and it's methods at compile time. The "Block Generator" in the Blockly binding can then use the extra attributes to hook in it's own callbacks for when instances of a class are created, and when values are set on property setters. In my first attempts to integrate Blockly with Three.js I had written alot of code to generate a custom block for each Three.js class. The new Block Generator can wrap a class and generate a custom block with a single call: block.bind_class( my_class ).

To run this demo you need the checkout the latest source code for: Pixi.js, GoogleBlockly, and Tween.js. Then pull the latest source for PythonJS. The source code for the demo is here.