Friday, September 27, 2013

PythonScript - Optimizations

Python is a very dynamic language and not easy to optimize. Ryan Kelly has a solution for this, he is the creator of library called Promise. Promise allows you to declare your functions are: invariant, constant, pure, and sensible. This removes many of the dynamic features of Python to improve performance using runtime byte-code optimizations. Using similar restrictions we can optimize the output of the PythonScript compiler to gain much higher performance.

PythonScript already performs quite well on purely numeric code, because this is translated into pure Javascript without any extra function calls. The Browser's JIT is able to unbox the purely numeric code and run it many times faster than regular Python. With non-numeric code PythonScript performs quite badly, running at least five times slower than regular Python. Each attribute access is wrapped with the get_attribute PythonJS function to properly emulate Python's dynamic model where an attribute can be class-level or instance-level. Making things slower still, each function call requires significant overhead in order to emulate Python's calling conventions, packing and unpacking: args, keyword args, variable args, variable keyword args.


To speed up attribute access we can use a new class decorator @inline, this tells the PythonScript compiler to bypass get_attribute and directly return the attribute from the instances __dict__. The compiler is smart enough to know that if the attribute is not found on the instance, and the class has defined a custom __getattr__ method, to directly inline and call __getattr__. The compiler knows which attributes are to be found on the instance by introspecting its methods, and checking for each time an attribute is assigned to self.

assert isinstance

In order for the PythonScript compiler to perform these inline optimizations, it needs to know which class an instance is. When a class instance is created, it knows that variable name is the given class, however if the instance is passed to a function that returns it and assigns it to a new variable, the compiler will no longer know what type it is. The workaround to manually define the type of an instance is using assert isinstance( a, SomeClass ). Another way to declare the type of a variable is using PythonScript's special var() function and using keyword arguments like this: var(x=SomeType), this feature was added in this commit.

class A:

a = A()
b = a ## for this simple case, the compiler still knows that "b" is an instance of A

def opaque(x):
  return x

c = opaque( b )  ## the compiler is not sure what "c" is
assert isinstance(c, A) ## this informs the compiler that "c" is an instance of A
... ## any following code using "c" will have inline optimizations.

Closure Compiler

In my fork of PythonScript I have changed it to be compatible with the Closure compiler. Using its "Advanced Optimizations" option, it is able to inline the body of simple functions, this provides another level of in-lining that can speed up the code even more. Closure is able to compress and optimize the code in other ways as well.


The test code below compiled with PythonScript+Closure and run in Google Chrome, is faster than PyPy by two times, and 32X faster than normal Python.
def somefunc():
  return 1

class A:
  def __init__(self):
    self.x = 1
    self.y = 2
    self.z = 3

  #def __getattr__(self,name):
  def __getattr__():
    ## a method that takes no arguments tells PythonScript
    ## to optimize the call for no arguments
    return random()

a = A()

now = time()
r = 0.0
i = 0
while i < 1000000:
  r += a.some_dynamic_attribute
  i += somefunc()

print( time()-now )

1 comment:

  1. Very interesting! Closure Compiler support is planned for the release after the next.