The Cost of Dynamism (was Re: Pyhon 2.x or 3.x, which is faster?)

BartC bc at freeuk.com
Thu Mar 24 15:54:54 EDT 2016


On 24/03/2016 18:10, Steven D'Aprano wrote:
> On Fri, 25 Mar 2016 12:01 am, BartC wrote:

>> Python 3 (on Windows) might take 200ns. Clisp is 1300ns (interpreted,
>> presumably). Ruby 170ns. Lua 80ns. Mine 10-20ns. Unoptimised C is 4ns,
>> but this is not executing code indirectly as most of the rest have to.
>
> "Might"? Are you making those numbers up?

No.

>> [Timings include loop overheads that need to be factored out.]
>
> Then those numbers are pointless.

Yes, they would need some adjustment to do this stuff properly. FWIW the 
loop overheads were about 30% in Python, 40% in Ruby, and 20% in mine.

> The Python figure, for example, might be
> 199ns for the loop overhead and 1ns for the function call, or 1ns for the
> loop overhead and 199ns for the function call. Or anything in between. How
> do you know which is which?

It sounds like they would both need some work!

>> So there might be room for improvement, but those faster languages are
>> also simpler.

> In the case of C, the line is limited to working with some specific type
> (say, an int32). Even there, if the addition might overflow, the behaviour
> is undefined and the compiler can do anything it wants (including time
> travel,

I'm pretty sure it can't do time travel...

  or erasing your hard disk).
>
> In the case of Python, the line will work with a potentially infinite number
> of types: int, float, complex, Fraction, Decimal, subclasses of all the
> above, custom numeric types, and anything else that quacks like a number.

Yes, it has to do some dynamic type dispatch. All the languages except C 
were dynamic (C cheats in so many ways).

However, my bit of code (a for-loop calling an empty function) doesn't 
on the surface, do anything that requires type dispatch, or does it?

This is where we come to the first differences between how Python works 
and how, say, my language works (I don't know about Lua, Ruby and Lisp.)

Python bytecode for empty function bill():

  4           0 LOAD_CONST               0 (None)
              3 RETURN_VALUE

Inner loop calling bill():

         >>   13 FOR_ITER                13 (to 29)
              16 STORE_FAST               0 (n)

   8          19 LOAD_GLOBAL              1 (bill)
              22 CALL_FUNCTION            0 (0 positional... )
              25 POP_TOP
              26 JUMP_ABSOLUTE           13


My bytecode for function bill():

0: 000   --------- return

My inner loop bytecode:

1: 005  %4:
1: 006   --------- call                         [&t.bill], 0
1: 006   --------- to_f                         %4, [t.start.av$1:-1]


Half the explanation is right here: I use 1 op in the function, and 2 in 
the loop. Python uses 2 in the function, and 6 in the loop.

(I use a dedicated repeat-N-times loop that needs no explicit loop 
variable, only an internal integer count. I use a special 'proc' form of 
function with no return value. And I use static functions so the 
byte-code knows the function being called, and knows it returns no value 
that needs to be popped.)

-- 
Bartc



More information about the Python-list mailing list