
Why Python is Slow: Looking Under the Hood (2014) - s16h
http://jakevdp.github.io/blog/2014/05/09/why-python-is-slow/
======
wruza
>[boxed values...] The dynamic typing means that there are a lot more steps
involved with any operation. This is a primary reason that Python is slow
compared to C for operations on numerical data.

Not exactly. Setting typecodes and vals doesn’t slow things down by many
orders of magnitude. The main reason python is relatively slow is that there
is no practical way to reason about what parts of program may be optimized out
or leveled down to native datatypes and then restructured in a very efficient
way. This is what optimizing/jit compilers do to achieve much performance;
this one is the source of x100, not unboxing on its own. Technically, tracing
jit that doesn’t care if you’re writing in static or dynamic, strict or duck
typing can be done for any language, but (afaik) python is not very jit-
friendly in general.

~~~
ryanplant-au
Does JavaScript make it easier to reason about those potentially-optimizable
areas? Which language features make it easier to optimize to the level that V8
is? (V8 being 7-10x faster than CPython 3 on most of the Benchmarks Game.)

~~~
chubot
As far as I understand, one important difference is that JavaScript doesn't
have __getattr__ or __setattr__ (or at least earlier versions didn't).

You might not use those hooks a lot in your application code, but it seems
that web frameworks and the like do a lot of reflection, which makes the code
difficult/impossible to optimize (even at runtime with a JIT).

Python also has __add__ (operator overloading) and JavaScript doesn't. This is
a good talk about how subtle or crufty the semantics of something like "a+b"
is in Python:

[https://www.youtube.com/watch?v=IeSu_odkI5I](https://www.youtube.com/watch?v=IeSu_odkI5I)

The PyPy developers had to copy a lot of the implementation details of
CPython, which doesn't always result in the fastest code.

~~~
mschaef
You should take a look at the Chambers and Ungar papers on the implementation
of Self. In Self, _everything_ is theoretically done through message
send/dispatch. This includes field access, numerical operations, flow
control... the works. (In a language with prototypical inheritance too.)

What's interesting is that by the time they're done with their optimizations,
they manage to get relatively close (x2, iirc) to native speed by essentially
inlining everything they can and keeping enough metadata around that they can
retain the dynamic properties of the languauge. Impressive stuff. (Which is
probably why Sun hired them early in the Java/JIT days.)

------
dnautics
Julia is all three, yet it's fast (allowing for jit, which in Julia is a one
time cost). It's worth noting that those properties themselves are not what's
critical, so much as designing around it and allowing for fast paths through
critical code (locking down the dynamic types, first class array datatypes
with packed forms)...

~~~
azag0
It's rather the degree to which Python is dynamic that makes it slow. PyPy
could be considered an implicit JIT compiler for Python, yet it is still far
slower than Julia. The level of magic you can apply to Python objects that the
interpreter/compiler must support is just a different league than Julia. I'd
be interested if someone could compare to JS.

~~~
pjmlp
Lisp, Smalltalk, Dylan and SELF allow for the same kind of magic.

The JIT developed for SELF is the genesis of Hotspot.

JRuby guys have a quite good implementation making use of Graal, and Ruby is
not less magical than Python.

In the end it boils down to how much the community prefers to keep on using C,
or improve PyPy.

EDIT: Fixed auto-correction induced typo.

~~~
shellac
s/Gradle/Graal/?

In the case of python it's clear that the heavy reliance on c extensions is a
blessing and a curse: it's kept python relevant in communities like science
even though it isn't very fast. However one of the lessons of Graal seems to
be that such extensions can seriously prohibit improving performance, since
they are opaque to JITs.

There's a few talks by Chris Seaton (e.g.
[https://www.youtube.com/watch?v=YLtjkP9bD_U](https://www.youtube.com/watch?v=YLtjkP9bD_U))
on the topic.

~~~
pjmlp
Yeah, typo due to auto-correction.

------
hasenj
Python is optimized for small programs being easy and quick (and pleasant) to
write.

Every other use case it fails in some way. For being slow. For lacking static
typing. For lacking compilation. For having a GIL. etc.

~~~
baldfat
DEPLOYMENT

My ONE HUGE Gripe with Python. This and that Pandas (I understand why BUT it
drove me away) Zero based for statistical work. Your math is 1 based and the
language is 0 based.

~~~
billfruit
Precisely the opposite reason R drove me away. R uses 1 based
indexing.Seriously we should all be using Zero based indexing for all
purposes.

~~~
peatmoss
One can make an appeal to aesthetics, but that’s no differentiator.

Or one could appeal to adherence to existing standards. Fortran is the oldest
(portable) programming language and was 1-based.

Or one could appeal to consistency with the problem domain. Again, math [EDIT:
indices are] 1-based.

Zero-based is consistent with C, but that’s about the best argument I can make
for zero-based.

~~~
DonbunEf7
Maths is zero-based. I'm not sure why you think that numbers start with one;
every formulation of the integers in modern maths starts with zero.

~~~
peatmoss
Matrices, common in statistics because of applications of linear algebra, have
long maintained the convention of i,j indexes starting with one.

------
lorenzfx
I believe this article [0] (previous discussion [1]) from one of pyston's
authors gives a _much_ better overview of why python is slow.

[0] [http://blog.kevmod.com/2016/07/why-is-python-
slow/](http://blog.kevmod.com/2016/07/why-is-python-slow/) [1]
[https://news.ycombinator.com/item?id=12025309](https://news.ycombinator.com/item?id=12025309)

------
mikebenfield
Although I use and enjoy Python for some purposes, I can't help by see all the
effort gone into improving Python performance (including Pypy, Cython,
rewriting code in C, etc) as fixing a self-inflicted wound. Why are we using a
language whose semantics make it very difficult to execute quickly?

~~~
wyldfire
Because virtually nothing is bound to anything until we get to this line of
code, Python is the ultimate dynamic language.

I find that the vast majority of Python code that I write is not processor-
bound or memory-bound. It's disk or network bound (and still would be if I
wrote it in C). It's also much simpler to teach programming in than
alternatives.

------
chmaynard
From the article: "Dynamic typing makes Python easier to use than C." The
author gives no justification for this claim. Do any language experts care to
comment?

~~~
winter_blue
I'm a big fan of strong static type systems. I believe type-safety increases
code quality significantly.

I used to think several years ago, that the main benefit of strong static
typing was code safety / eliminating a whole class of bugs. But I've changed
my opinion. I now think the biggest benefit is that it makes the code _a lot
easier_ for other people to read and understand.

I mean I have multiple personal projects where I've used Python (which is a
dynamically typed language), but these are _small one-off_ projects. But I
think when working in a team, especially a large team, having types becomes a
huge thing. Having types for objects is especially useful. Having types forces
you to think more clearly about the structure of your data.

It's really sad when I see `foo(bar)`, and I have no idea what the type of
`bar` is, and if it's an object, I have no idea what fields `bar` has. I have
to simply guess the structure of the various implicit types by looking at the
code (sigh). It makes the code difficult to read, and rather unpleasant to
work on. Not to mention, all the multitude of bugs that come from duck/dynamic
typing.

I don't think good statically typed languages are hard to use at all. Type
inference has spread everywhere that the old argument of having to repeat your
types doesn't hold anymore. TypeScript, Flow (JavaScrpt), Haskell, languages
from the ML family are really good at type inference. Even the `auto` type
inference in C++17 was better than I'd expected.

~~~
u801e
> It's really sad when I see `foo(bar)`, and I have no idea what the type of
> `bar` is, and if it's an object, I have no idea what fields `bar` has

With a statically typed language, you still have to look through the code to
find the definition of an object. I believe that can be avoided by having easy
to access to documentation which would also apply to dynamically typed
languages like Python.

~~~
winter_blue
_> you still have to look through the code to find the definition of an
object_

I haven't done this in the last 10 years. I typically use IDEs, and every IDE
I've used has had a _" Go To Definition/Declaration"_ feature, and let you set
a key binding for it. On JetBrains' IDEs, I've gotten quite used to pressing
Ctrl+B to jump to a type definition's, and then pressing Alt+← to jump back to
where I was originally in the code.

~~~
flavio81
> _I typically use IDEs, and every IDE I 've used has had a "Go To
> Definition/Declaration" feature_

This is also available in some dynamically typed languages, working just fine.

~~~
dahauns
"working just fine".

No, not even close to the same level.

------
adenadel
I thought this was pretty neat

    
    
      # WARNNG: never do this!
      id113 = id(113)
      iptr = IntStruct.from_address(id113)
      iptr.ob_digit = 4  # now Python's 113 contains a 4!
    
      113 == 4
    

And now since the proper value 113 doesn't exist you have to resort to the
binary representation on your system to revert back to normal behavior

    
    
      ctypes.cast(id113, ctypes.POINTER(ctypes.c_char))[3 * 8] = b'\x71'

~~~
whyever
After trying this, 113 == 4 is still False for me.

~~~
quadratoc
There was a slight typo in the article, the line of code should be
ctypes.cast(id(113), ctypes.POINTER(ctypes.c_char))[3 * 8] = b'\x71'

------
nayuki
I agree with this article. In practice, when writing number-crunching code in
Python versus Java, I found that Python is usually 10 to 30× slower than Java,
sometimes even 100× slower. See: [https://www.nayuki.io/page/project-euler-
solutions#benchmark...](https://www.nayuki.io/page/project-euler-
solutions#benchmark-timings)

------
ryanpcmcquen
Counter? [https://hackernoon.com/yes-python-is-slow-and-i-dont-
care-13...](https://hackernoon.com/yes-python-is-slow-and-i-dont-
care-13763980b5a1)

------
tjpnz
And yet there are entire industries built on the back of it. Everytime you see
a a movie there'll be some Python code somehow responsible for the pixels
you're seeing on the big screen.

~~~
criley2
This is perhaps a great example of why "speed" is a poorly descriptive term
for software.

In automobiles, we wouldn't call a large truck "fast" even though it has a
much larger (and more "performant") engine than a small car. That small car is
likely "much faster" than the truck, and yet cannot do most of the things the
truck does.

Sure, python is popular and important, but I don't think that popularity and
"speed" are necessarily all that connected at all, except in use-cases where
speed is the most important factor (say, financial transactions).

When overnight rendering 3d graphics, speed is important but final product and
ease of use are probably more important, since you can compensate for speed
with a larger render farm. But more bank server aren't necessarily going to
reduce transaction latency (in fact could increase it) so the gains there must
often be at a much lower level.

------
jokoon
How compatible are existing python libraries with pypy, and is the official
python taking clues from pypy? Is there more work to do to make pypy even
faster?

~~~
dr_zoidberg
Some complex libraries needed special porting (eg. NumPyPy), and there was
work under way to get rid of that and provide a CPython compatible interface.

The CPython team does take clues from PyPy (eg: see the CPy3.6 dict
implementation), but they are also a lot more careful (some might say
"slower") to adopt changes. Both teams also seem to have some ideological
differences on how to face the long term development of the language (usualy,
GvR goes for "simpler" instead of "more performant").

------
baybal2
A thing much bigger than GIL for Python is that in python bytecode, objects
are used as primitives.

------
anon1253
Except it isn't really. Yes, Python is incredibly slow for day to day stuff,
but the sheer amount of easy to use numerical libraries (numpy, scipy, scikit-
learn, tensorflow, keras, opencv, just to name a few) make it one of the
fastest out there for numerical computation. I tried doing some numerical
heavy stuff on the JVM (with Java and Clojure) and it fights you every step of
the way ... and that has static typing and all the things the article
mentions. Of course, Python derives that functionality from C and Fortran …
but just having that interop at your fingertips is magical in terms of
productivity. I still get nightmares from working with the JNI.

~~~
pjmlp
The point is not having to write C and Fortran in first place.

~~~
dr_zoidberg
As a heavy user of numpy, I _use_ a lot of C and Fortran code, _without having
to write it_.

------
lispm
> Python being a dynamically typed, interpreted language

'CPython' is the defining implementation of the dynamically typed language
'Python' using a byte-code VM (and no jit compiler).

    
    
      bash-3.2$ time /tmp/bench.py
      5000000050000000
    
      real    0m19.763s
      user    0m15.015s
      sys     0m4.309s
    

'SBCL' is an implementation of the dynamically typed language 'Common Lisp'
using a native code compiler.

    
    
      bash-3.2$ time /tmp/bench.lisp
      5000000050000000
    
      real    0m0.319s
      user    0m0.294s
      sys     0m0.017s
    

The unoptimized code is roughly 65 times faster in SBCL compared to CPython -
including startup time.

~~~
exikyut
This is an unreproducible benchmark. Can we have the programs you used?

~~~
lispm
See the comments in the original article for the python examples...

The Lisp program is basically this:

    
    
      (format t "~a~%" (loop for i from 1 upto 100000000 sum i))
    

The Python code can be made a lot faster by using xrange and also reduce.

But then the SBCL compiler can optimize a type declared version down to 0.07
seconds runtime for the script.

    
    
      (locally (declare (optimize (speed 3)
                                  (debug 0)
                                  (safety 0)))
       (format t "~a~%" (loop for i fixnum from 1 upto 100000000
                              sum i of-type fixnum)))

~~~
acdha
> The Python code can be made a lot faster by using xrange and also reduce.

This is a key distinction since it reveals that most of the difference is due
to these programs doing different things.

Changing this to compare the same thing shows why this matters:

    
    
        cadams@jupiter:~ $ sbcl --version
        SBCL 1.4.0
        cadams@jupiter:~ $ python2.7 --version
        Python 2.7.14
        cadams@jupiter:~ $ pypy --version
        Python 2.7.13 (84a2f3e6a7f88f2fe698e473998755b3bd1a12e2, Oct 05 2017, 16:34:13)
        [PyPy 5.9.0 with GCC 4.2.1 Compatible Apple LLVM 9.0.0 (clang-900.0.37)]
    
        cadams@jupiter:~ $ time ./test.lisp 
        5000000050000000
    
        real	0m0.209s
        user	0m0.194s
        sys	0m0.011s
    
        cadams@jupiter:~ $ time python2.7 test.py
        5000000050000000
    
        real	0m0.758s
        user	0m0.744s
        sys	0m0.008s
    
        cadams@jupiter:~ $ time pypy test.py
        5000000050000000
    
        real	0m0.123s
        user	0m0.101s
        sys	0m0.019s
    

So at the end of that we've discovered two things we already knew: an
interpreter is slower than a JIT given enough work to balance the startup
time, and that allocating a list with millions of items and then immediately
discarding it is more expensive than summing an iterator.

Since Python 3 made range() lazy by default, the core developers clearly agree
that this is better than allocating lists unless explicitly requested.

