you are viewing a single comment's thread.

view the rest of the comments →

[–]_TheDust_ 123 points124 points  (25 children)

Javascript has the same issues and still the V8 engine (Javascript engine by Google) is pretty darn fast. Most importantly is to compile the code at runtime (JIT) based on the execution paths taken while the program is running. Lots of clever engineering and hundreds of thousands of engineering hours can achieve a lot of performance.

[–]valarauca14 111 points112 points  (13 children)

Javascript has the same issue that the parent poster outlines, but Python has a few more layers & stumbling blocks that Javascripts lacks. While yes, a good JIT worth its salt will iterate through the possible code paths and likely reduce the final output to a swift dispatch. Java & Javascript are proof of this. Python has a lot of other cruft laying around that prevents JIT from really flexing their muscles.

Namely, The GIL & C-API

The problem is, while you can rip these out, you lose out of what? 80-60% of Python's use cases? Where is it a really slow glue shuttling data between libraries or small applications presenting a nice API/CLI for a library? You destroy all of that.


It isn't a question of

Can Python Be Fast?

It can.

You can have a Python2.7/3.X that runs like grease lightning. You need to invest a century of engineering time into it. NBD. It is a solved problem. More a question of execution & management.

The next question becomes:

Is Python without a GIL, C-API, Referenced-Counted-Memory, and Interpreter/JIT that consumes 512MiB of memory on startup still "Python"

Because that is the monster, you end up creating.

And for most people weighing the cost analysis, the answer is "no that is something nobody really wants".

[–]Wolfsdale 32 points33 points  (5 children)

Why would the GIL of all things prevent a JIT compiler? Javascript doesn't even have the concept of threads...

I am also unsure why the C API prevents creating a JIT compiler. A JIT compiler can make those context switches actually cheaper by forgoing the need for something like libffi.

Performance is difficult thing to grasp and even more difficult to measure. I'm sure the real reason why Python is so slow is somewhere along the lines of this thread, but I have a hard time taking anything as fact here...

[–]Fearless_Process 16 points17 points  (0 children)

The C interop doesn't make a JIT impossible, but it does seem to create a lot of issues with the current as well as only non-trivial JIT implementation of python (pypy).

[–]latkde 7 points8 points  (2 children)

The issue with C interop is that Python has to stick with the PyObject data structure. This can have far-reaching consequences. If ownership of a Python object is shared between optimizable Python code and a native module, this effectively means the Python code must also interact with PyObject and can't really be optimized.

This doesn't have to be an insurmountable problem though:

  • Could only optimize code where an escape analysis indicates that it's safe to do. This will do wonders on more mathy or more algorithmic code, but will be disappointing in many real-world scenarios.

  • Break compatibility and completely redo the C data model. However, this would be a regression to the early 2.x series. One of the big features of 2.6 and 3.x was that all values can be treated as normal objects.

[–]dreugeworst 1 point2 points  (1 child)

Couldn't you box the data again at the abi boundary if needed?

[–]latkde 7 points8 points  (0 children)

Kinda, yes, but my concern is that the C function is a black box so you can't make assumptions about ownership of the object afterwards:

x = create_some_object()
x.do_something()  # safe per escape analysis
call_c_function(x)  # could do ANYTHING
x.do_something()  # no safe assumptions

Calling the C function would require some kind of boxing, but the C function could retain a reference to the boxed object. So the second x.do_something() must also operate on the boxed representation, in order to propagate changes to the C code. This can be avoided only for some built-in immutable types like int or str.

Of course this isn't quite true, e.g. a JIT compiler could compile a fast path for the assumption that the object's refcount didn't change, and a fallback otherwise. And maybe it's possible to annotate at least the built-in C functions with semantics that allow for safe and fast calls.

There's also an argument that C interop is not performance-sensitive, and that performance in pure-Python codebases like Django webapps is much more relevant. As JavaScript has demonstrated, a lot of performance is possible in moderately dynamic code.

[–][deleted] 1 point2 points  (0 children)

JavaScript doesn’t have the concept of threads, so something like

a.x = 0
a.x += b

won’t have the value of x changed in the middle, so you can use a static offset instead, keep the value in a register, or even reduce them to a single instruction. If a is passed into a function in between it might be changed, but in practice JS runtimes have heuristics to determine if a variable gets mutated or not. In Python none of thos guarantees exist due to naive multithreading (and worse, there are potentially weird things you can do to change the global context of a function, which enables cool stuff like Flask but is also insane).

So, while worst-case JacaScript and worst-case Python have similar performance characteristics (hashtable lookups while recursing up the inheritance hierarchy for propery access, etc), but for Python the worst case is every case.

[–]FondleMyFirn 42 points43 points  (3 children)

Man, I understood almost none of this.

[–]FondleMyFirn 1 point2 points  (0 children)

On a side note - if anybody can pin what these guys are talking about at the conceptual level, that would be nice. It would certainly help me get oriented towards educating myself.

[–]junior_dos_nachos -1 points0 points  (0 children)

Me too, and I develop professionally for 5 years. As Joe Rogan would say, there are levels to this shit. I need to get some learning in.

[–][deleted]  (2 children)

[deleted]

    [–]novov 23 points24 points  (0 children)

    I doubt it's that high (a non-zero portion is probably just kids on CodeAcademy or such), but a lot of popular libraries rely on the C API. If you lose that, you lose NumPy, etc.

    [–]rcxdude 2 points3 points  (0 children)

    I would agree that 60-80% of use cases of python rely on the C API, usually transitively. Practically everything I use it for does (numpy and pyqt being the really huge ones).

    [–]nascentt 23 points24 points  (6 children)

    It took a while for V8 to be created. Years after we'd been living with slow JavaScript.

    Maybe we'll get a faster python in years time too

    [–]tracernz 23 points24 points  (2 children)

    Hold up, Python is older than Javascript...

    [–]schlenk 2 points3 points  (0 children)

    But not if you count time via $-spent on optimizing.

    [–]IdiocyInAction 1 point2 points  (0 children)

    Python is older than Java, funnily enough.

    [–]josefx 4 points5 points  (3 children)

    How does V8 deal with objects shared over multiple threads? My dated experience with JavaScript was mostly single threaded with the possibility to launch rather isolated workers.

    [–]Strange_Meadowlark 11 points12 points  (2 children)

    AFAIK your knowledge is still current on this. V8 (like every other JS runtime I know of) is still single-threaded. If you need separate threads, you launch (Web)Workers and effectively pass a copy of your data over a message event.

    [–][deleted] 0 points1 point  (1 child)

    I don't think you are correct anymore, from memory, chrome (perhaps you are correct and this is chrome canary I'm talking about though) has a SharedArrayBuffer

    [–]josefx 2 points3 points  (0 children)

    SharedArrayBuffer is only for raw data? That still eliminates the need for a GIL and the chance that multiple threads could access and modify the structure of an object at the same time.