interpreter vs. compiled

castironpi castironpi at gmail.com
Wed Jul 30 02:40:16 EDT 2008


I note that IronPython and Python's pickle.dumps do not return the
same value.  Perhaps this relates to the absence of interpreter loop.

>>> p.dumps( { 'a': True, 'b': set( ) } )
IPy: '(dp0\nVb\np1\nc__builtin__\nset\np3\n((lp4\ntp5\nRp2\nsVa
\np6\nI01\ns.'
CPy: "(dp0\nS'a'\np1\nI01\nsS'b'\np2\nc__builtin__\nset
\np3\n((lp4\ntp5\nRp6\ns."

You make me think of a more elaborate example.

for k in range( 100 ):
  i= j()
  g= h+ i
  e= f+ g
  c= d+ e
  a= b+ c

Here, j creates a new class dynamically, and returns an instance of
it.  Addition is defined on it but the return type from it varies.

If I read you correctly, IPy can leave hundreds of different addition
stubs laying around at the end of the for-loop, each of which only
gets executed once or twice, each of which was compiled for the exact
combination of types it was called for.

I might construe this to be a degenerate case, and the majority of
times, you'll reexecute stubs enough to outweigh the length of time
the compilation step takes.  If you still do the bounds checking, it
takes extra instructions (C doesn't), but operation switch-case
BINARY_ADD, (PyInt_CheckExact(v) && PyInt_CheckExact(w)), and POP and
TOP, are all handled by the selection of stubs from $addSite.

I'm read from last April:
>>> The most interesting cases to me are the 5 tests where CPython is more than 3x faster than IronPython and the other 5 tests where IronPython is more than 3x faster than CPython.  CPython's strongest performance is in dictionaries with integer and string keys, list slicing, small tuples and code that actually throws and catches exceptions.  IronPython's strongest performance is in calling builtin functions, if/then/else blocks, calling python functions, deep recursion, and try/except blocks that don't actually catch an exception.
<<< http://lists.ironpython.com/pipermail/users-ironpython.com/2007-April/004773.html

It's interesting that CPython can make those gains still by using a
stack implementation.

I'll observe that IronPython has the additional dependency of the
full .NET runtime.  (It was my point 7/18 about incorporating the GNU
libs, that to compile to machine-native, as a JIT does, you need the
instruction set of the machine.)   Whereas, CPython can disregard
them, having already been compiled for it.

I think what I was looking for is that IronPython employs the .NET to
compile to machine instructions, once it's known what the values of
the variables are that are the operands.  The trade-off is compilation
time + type checks + stub look-up.

What I want to know is, if __add__ performs an attribute look-up, is
that optimized in any way, after the IP is already in compiled code?

After all that, I don't feel so guilty about stepping on Tim's toes.

On Jul 30, 12:12 am, Dino Viehland <di... at exchange.microsoft.com>
wrote:
> IronPython doesn't have an interpreter loop and therefore has no POP / TOP / etc...   Instead what IronPython has is a method call Int32Ops.Add which looks like:
>
>         public static object Add(Int32 x, Int32 y) {
>             long result = (long) x + y;
>             if (Int32.MinValue <= result && result <= Int32.MaxValue) {
>                 return Microsoft.Scripting.Runtime.RuntimeHelpers.Int32ToObject((Int32)(result));
>             }
>             return BigIntegerOps.Add((BigInteger)x, (BigInteger)y);
>         }
>
> This is the implementation of int.__add__.  Note that calling int.__add__ can actually return NotImplemented and that's handled by the method binder looking at the strong typing defined on Add's signature here - and then automatically generating the NotImplemented result when the arguments aren't ints.  So that's why you don't see that here even though it's the full implementation of int.__add__.
>
> Ok, next if you define a function like:
>
> def adder(a, b):
>         return a + b
>
> this turns into a .NET method, which will get JITed, which in C# would look something like like:
>
> static object adder(object a, object b) {
>     return $addSite.Invoke(a, b)
>
> }
>
> where $addSite is a dynamically updated call site.
>
> $addSite knows that it's performing addition and knows how to do nothing other than update the call site the 1st time it's invoked.  $addSite is local to the function so if you define another function doing addition it'll have its own site instance.
>
> So the 1st thing the call site does is a call back into the IronPython runtime which starts looking at a & b to figure out what to do.  Python defines that as try __add__, maybe try __radd__, handle coercion, etc...  So we go looking through finding the __add__ method - if that can return NotImplemented then we find the __radd__ method, etc...  In this case we're just adding two integers and we know that the implementation of Add() won't return NotImplemented - so there's no need to call __radd__.  We know we don't have to worry about NotImplemented because the Add method doesn't have the .NET attribute indicating it can return NotImplemented.
>
> At this point we need to do two things.  We need to generate the test which is going to see if future arguments are applicable to what we just figured out and then we need to generate the code which is actually going to handle this.  That gets combined together into the new call site delegate and it'll look something like:
>
> static void CallSiteStub(CallSite site, object a, object b) {
>         if (a != null && a.GetType() == typeof(int) && b != null && b.GetType() == typeof(int)) {
>             return IntOps.Add((int)a, (int)b);
>         }
>         return site.UpdateBindingAndInvoke(a, b);
>
> }
>
> That gets compiled down as a lightweight dynamic method which also gets JITed.  The next time through the call site's Invoke body will be this method and things will go really fast if we have int's again.  Also notice this is looking an awful lot like the inlined/fast-path(?) code dealing with int's that you quoted.  If everything was awesome (currently it's not for a couple of reasons) the JIT would even inline the IntOps.Add call and it'd probably be near identical.  And everything would be running native on the CPU.
>
> So that's how 2 + 2 works...  Finally if it's a user type then we'd generate a more complicated test like (and getting more and more pseudo code to keep things simple):
>
> if (PythonOps.CheckTypeVersion(a, 42) && PythonOps.CheckTypeVersion(b, 42)) {
>     return $callSite.Invoke(__cachedAddSlot__.__get__(a), b);
>
> }
>
> Here $callSite is another stub which will handle doing optimal dispatch to whatever __add__.__get__ will return.  It could be a Python type, it could be a user defined function, it could be the Python built-in sum function, etc...  so that's the reason for the extra dynamic dispatch.
>
> So in summary: everything is compiled to IL.  At runtime we have lots of stubs all over the place which do the work to figure out the dynamic operation and then cache the result of that calculation.
>
> Also what I've just described is how IronPython 2.0 works.  IronPython 1.0 is basically the same but mostly w/o the stubs and where we use stub methods they're much less sophisticated.
>
> Also, IronPython is open source -www.codeplex.com/IronPython
>
> -----Original Message-----
abriged:
> This is from 7/22/08, same author:
> > I wouldn't say "can't".  The current CPython VM does not compile
> > code.  It COULD.  The C#/.NET VM does.
>
> Three big claims here that I breezed right over and didn't believe.
>
> > It COULD.
>
> I'm evidently assuming that if it could, it would.
>
> > The current CPython VM does not compile code.
>
> Therefore it couldn't, or the assumption is wrong.  Tim says it is.
> And the glaring one--
>
> WHY NOT?  Why doesn't CPython do it?
>
> I am imagining that every Python implementation has something like
> it.  If IronPython does not, in particular, not have the 'POP();
> TOP();' sequence, then it isn't running on a stack machine.  Is the
> IronPython code open source, and can someone link to it?  I'm not
> wading through it from scratch.  What does it have instead?  Does
> dynamic typing still work?



More information about the Python-list mailing list