From 5831872df5ba4717313d4c8ca40753c8986340b8 Mon Sep 17 00:00:00 2001 From: hartsantler Date: Tue, 10 Dec 2013 00:33:25 -0800 Subject: [PATCH] fixed inline functions, updated README --- README.rst | 15 +--- pythonjs/python_to_pythonjs.py | 62 ++++++++----- tests/nbody_oo_fast.html | 159 +++++++++++++++++++++++++++++++++ tests/server.py | 15 ++++ 4 files changed, 220 insertions(+), 31 deletions(-) create mode 100644 tests/nbody_oo_fast.html diff --git a/README.rst b/README.rst index c2a81a2..97e06b8 100644 --- a/README.rst +++ b/README.rst @@ -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 --------------- @@ -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 --------------- diff --git a/pythonjs/python_to_pythonjs.py b/pythonjs/python_to_pythonjs.py index b38919f..a76f9d1 100755 --- a/pythonjs/python_to_pythonjs.py +++ b/pythonjs/python_to_pythonjs.py @@ -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 @@ -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) @@ -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 @@ -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': @@ -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: @@ -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': diff --git a/tests/nbody_oo_fast.html b/tests/nbody_oo_fast.html new file mode 100644 index 0000000..be66a6f --- /dev/null +++ b/tests/nbody_oo_fast.html @@ -0,0 +1,159 @@ + + + + + + + + + + + \ No newline at end of file diff --git a/tests/server.py b/tests/server.py index 71271a8..0b4d4f4 100755 --- a/tests/server.py +++ b/tests/server.py @@ -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 @@ -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 ):