Skip to content

Commit

Permalink
fixed inline functions, updated README
Browse files Browse the repository at this point in the history
  • Loading branch information
hartsantler committed Dec 10, 2013
1 parent 843f74e commit 5831872
Show file tree
Hide file tree
Showing 4 changed files with 220 additions and 31 deletions.
15 changes: 4 additions & 11 deletions README.rst
Expand Up @@ -31,13 +31,12 @@ each section of your code, where you need performance you
can disable operator overloading, and other slow operations.
Features can be switched off and on for blocks of code using
`pythonjs.configure()` or the special `with` statements and
decorators described below. When PythonJS is run in fast
mode (javascript with inline functions) it beats PyPy in the
Richards, Pystone, and N-Body benchmarks.
decorators described below. When PythonJS is run in fastest
mode (javascript mode) it beats PyPy in the Richards, and N-Body benchmarks.

.. image:: http://1.bp.blogspot.com/-Pn7jNLT-Suo/UphWdu48WiI/AAAAAAAAAis/n5HgVFJwgH8/s400/nbody.png
.. image:: http://2.bp.blogspot.com/-pylzspKRu6M/UqbAv3qIGTI/AAAAAAAAAkE/NnsAM5DZ_8M/s400/nbody.png

N-Body benchmark, PythonJS in javascript mode with inlined functions is 10x faster than PyPy2.2
N-Body benchmark, PythonJS in javascript mode has similar performance to Dart.

NodeJS
---------------
Expand Down Expand Up @@ -338,12 +337,6 @@ Python vs JavaScript Modes

PythonJS has two primary modes you can write code in: "python" and "javascript". The default mode is "python", you can mark sections of your code to use either mode with "pythonjs.configure(javascript=True/False)" or nesting blocks inside "with python:" or "with javascript:". The "javascript" mode can be used for sections of code where performance is a major concern. When in "javascript" mode the literal "[]" syntax will return a JavaScript Array instead of a PythonJS list, and a literal "{}" returns a JavaScript Object instead of a PythonJS dict. In both modes you can directly call external JavaScript functions, its only faster in "javascript" mode because function calls are direct without any wrapping.

.. image:: http://3.bp.blogspot.com/-Bl9I6tGEEMU/UqHIEK90bsI/AAAAAAAAAjk/0zn77pKXURU/s400/pystoned-modes.png

The Pystone benchmark is 800 times faster in "javascript" mode with inlined functions than normal "python" mode.

If your familiar with the details of the JavaScript language you might expect JavaScript rules to apply when in "javascript" mode, however they do not, it is still Python style. For example, in true JavaScript a for-in loop over an Array yields the indices, and a for-in loop over an Object yields the values - this is the opposite of what happens in Python and PythonJS.


Directly Calling JavaScript Functions
---------------
Expand Down
62 changes: 42 additions & 20 deletions pythonjs/python_to_pythonjs.py
Expand Up @@ -199,6 +199,7 @@ def __init__(self, source=None, module=None, module_path=None, dart=False):
self._with_inline = False
self._inline = []
self._inline_ids = 0
self._inline_breakout = False
self._js_classes = dict()
self._in_js_class = False

Expand Down Expand Up @@ -658,7 +659,7 @@ def _visit_js_classdef(self, node):
if line: writer.write( line )
#writer.write('%s.prototype.%s = %s'%(name,mname,mname))
f = 'function () { return %s.prototype.%s.apply(arguments[0], Array.prototype.slice.call(arguments,1)) }' %(name, mname)
writer.write('%s.%s = %s'%(name,mname,f))
writer.write('%s.%s = JS("%s")'%(name,mname,f))

for base in node.bases:
base = self.visit(base)
Expand Down Expand Up @@ -870,14 +871,15 @@ def visit_Return(self, node):
else:
if self._inline:
writer.write('__returns__%s = %s' %(self._inline[-1], self.visit(node.value)) )
writer.write('break')
if self._inline_breakout:
writer.write('break')
else:
writer.write('return %s' % self.visit(node.value))

else:
if self._inline:
writer.write('break')
pass ## TODO put inline inside a while loop that iterates once? and use `break` here to exit early?
if self._inline_breakout:
writer.write('break')
else:
writer.write('return') ## empty return

Expand All @@ -899,7 +901,10 @@ def visit_BinOp(self, node):
return '%s( [%s, %s], JSObject() )' %(op, left_operand, right_operand)

elif op == '%' and isinstance(node.left, ast.Str):
return '__sprintf( %s, %s[...] )' %(left, right) ## assumes that right is a tuple, or list.
if self._with_js:
return '__sprintf( %s, %s )' %(left, right) ## assumes that right is a tuple, or list.
else:
return '__sprintf( %s, %s[...] )' %(left, right) ## assumes that right is a tuple, or list.

elif op == '*' and isinstance(node.left, ast.List):
if len(node.left.elts) == 1 and isinstance(node.left.elts[0], ast.Name) and node.left.elts[0].id == 'None':
Expand Down Expand Up @@ -1390,23 +1395,40 @@ def inline_function(self, node):
if n in finfo['typedefs']:
self._func_typedefs[ remap[n] ] = finfo['typedefs'][n]

for i,a in enumerate(node.args):
b = self.visit( fnode.args.args[i] )
b = remap[ b ]
writer.write( "%s = %s" %(b, self.visit(a)) )
offset = len(fnode.args.args) - len(fnode.args.defaults)
for i,ad in enumerate(fnode.args.args):
if i < len(node.args):
ac = self.visit( node.args[i] )
else:
assert fnode.args.defaults
dindex = i - offset
ac = self.visit( fnode.args.defaults[dindex] )

self._inline.append( name )
ad = remap[ self.visit(ad) ]
writer.write( "%s = %s" %(ad, ac) )

writer.write("JS('var __returns__%s = null')"%name)
writer.write('while True:')
writer.push()
for b in fnode.body:
self.visit(b)
if len( finfo['return_nodes'] ) == 0:
writer.write('break')
writer.pull()

if self._inline.pop() != name:
return_id = name + str(self._inline_ids)
self._inline.append( return_id )

writer.write("JS('var __returns__%s = null')"%return_id)
#if len( finfo['return_nodes'] ) > 1: ## TODO fix me
if True:
self._inline_breakout = True
writer.write('while True:')
writer.push()
for b in fnode.body:
self.visit(b)

if not len( finfo['return_nodes'] ):
writer.write('break')
writer.pull()
#self._inline_breakout = False
else:
for b in fnode.body:
self.visit(b)

if self._inline.pop() != return_id:
raise RuntimeError

for n in remap:
Expand All @@ -1415,7 +1437,7 @@ def inline_function(self, node):
if n.id == gname:
n.id = n
log('###########inlined %s###########'%name)
return '__returns__%s' %name
return '__returns__%s' %return_id

def visit_Call(self, node):
if isinstance(node.func, ast.Attribute) and isinstance(node.func.value, Name) and node.func.value.id == 'pythonjs' and node.func.attr == 'configure':
Expand Down
159 changes: 159 additions & 0 deletions tests/nbody_oo_fast.html
@@ -0,0 +1,159 @@
<html>
<head>
<script src="pythonscript.js"></script>

<script type="text/python">
# The Computer Language Shootout Benchmarks
# http://shootout.alioth.debian.org/
#
# contributed by Kevin Carson

from time import time
from math import sqrt


pythonjs.configure( javascript=True )

pi = 3.14159265358979323
solar_mass = 4 * pi * pi
days_per_year = 365.24

class body:
def __init__(self, x,y,z, vx,vy,vz, mass):
self.x = x
self.y = y
self.z = z
self.vx = vx
self.vy = vy
self.vz = vz
self.mass = mass


def advance(bodies, dt):
for na in range( bodies.length ):
a = bodies[na];
nb = na + 1
for nb in range(na+1, bodies.length):
b = bodies[nb];

dx = a.x - b.x
dy = a.y - b.y
dz = a.z - b.z
d2 = dx * dx + dy * dy + dz * dz;
mag = dt / (d2 * sqrt(d2));

a.vx -= dx * b.mass * mag;
b.vx += dx * a.mass * mag;

a.vy -= dy * b.mass * mag;
b.vy += dy * a.mass * mag;

a.vz -= dz * b.mass * mag;
b.vz += dz * a.mass * mag;

for b in bodies:
b.x += dt * b.vx
b.y += dt * b.vy
b.z += dt * b.vz


def energy( bodies ):
e = 0.0;
for i in range( bodies.length ):
bi = bodies[ i ]
e += 0.5 * bi.mass * (bi.vx * bi.vx + bi.vy * bi.vy + bi.vz * bi.vz)

for j in range( i+1, bodies.length ):
bj = bodies[ j ]
dx = bi.x - bj.x
dy = bi.y - bj.y
dz = bi.z - bj.z
e -= (bi.mass * bj.mass) / sqrt(dx * dx + dy * dy + dz * dz)
return e


def offset_momentum(bodies):
global sun
px = py = pz = 0.0

for b in bodies :
px += b.vx * b.mass
py += b.vy * b.mass
pz += b.vz * b.mass

sun.vx = - px / solar_mass
sun.vy = - py / solar_mass
sun.vz = - pz / solar_mass

sun = body(0,0,0, 0,0,0, solar_mass)

jupiter = body(
4.84143144246472090e+00,
-1.16032004402742839e+00,
-1.03622044471123109e-01,
1.66007664274403694e-03 * days_per_year,
7.69901118419740425e-03 * days_per_year,
-6.90460016972063023e-05 * days_per_year,
9.54791938424326609e-04 * solar_mass
)

saturn = body(
8.34336671824457987e+00,
4.12479856412430479e+00,
-4.03523417114321381e-01,
-2.76742510726862411e-03 * days_per_year,
4.99852801234917238e-03 * days_per_year,
2.30417297573763929e-05 * days_per_year,
2.85885980666130812e-04 * solar_mass
)

uranus = body(
1.28943695621391310e+01,
-1.51111514016986312e+01,
-2.23307578892655734e-01,
2.96460137564761618e-03 * days_per_year,
2.37847173959480950e-03 * days_per_year,
-2.96589568540237556e-05 * days_per_year,
4.36624404335156298e-05 * solar_mass
)

neptune = body(
1.53796971148509165e+01,
-2.59193146099879641e+01,
1.79258772950371181e-01,
2.68067772490389322e-03 * days_per_year,
1.62824170038242295e-03 * days_per_year,
-9.51592254519715870e-05 * days_per_year,
5.15138902046611451e-05 * solar_mass
)

def test() :
n = 20000
bodies = [sun, jupiter, saturn, uranus, neptune]
#offset_momentum(bodies)

t0 = time()
start_e = energy(bodies)
for i in xrange(n) :
advance(bodies, 0.01)
end_e = energy(bodies)
t1 = time()-t0
print 'start energy:', start_e
print 'end energy:', end_e
return t1

def main():
times = []
for i in range(5):
t = test()
times.append( t )
print 'seconds:', times


</script>

</head>
<body>
<button id="mybutton" onclick="main()">click me</button>
</body>
</html>
15 changes: 15 additions & 0 deletions tests/server.py
Expand Up @@ -30,6 +30,8 @@
dart2js = os.path.expanduser( '~/dart/dart-sdk/bin/dart2js'),
dartanalyzer = os.path.expanduser( '~/dart/dart-sdk/bin/dartanalyzer'),

closure = os.path.expanduser( '~/closure-compiler/compiler.jar'),

)

DART = '--dart' in sys.argv ## force dart mode
Expand Down Expand Up @@ -87,6 +89,19 @@ def pythonjs_to_javascript( src ):
)
stdout, stderr = p.communicate( src.encode('utf-8') )
a = stdout.decode('utf-8')


if False and os.path.isfile( PATHS['closure'] ):
x = '/tmp/closure-input.js'; y = '/tmp/closure-output.js';
f = open(x, 'wb'); f.write( a.encode('utf-8') ); f.close()
subprocess.call([
'java', '-jar', PATHS['closure'],
#'--compilation_level', 'ADVANCED_OPTIMIZATIONS',
'--js', x, '--js_output_file', y,
'--formatting', 'PRETTY_PRINT',
])
f = open(y, 'rb'); a = f.read().decode('utf-8'); f.close()

return a

def python_to_javascript( src, module=None, dart=False, debug=False, dump=False ):
Expand Down

0 comments on commit 5831872

Please sign in to comment.