Hacker Newsnew | past | comments | ask | show | jobs | submitlogin

Is the performance difference of Python and Javascript due to the language or due to the runtime?

If one would compile both, the Python version and the Javascript version, to WebAssembly and execute that, how would the performance differ then?



The WebAssembly version of this exact test takes about twice as long as running natively. I just happened to do exactly this benchmark a few minutes ago:

Python 3.11.0 native: 0m43.755s

Python 3.11.0 WebAssembly (on node.js): 1m31.305s (about 2.1x as long).

For what it is worth, I also maintain a Python --> Javascript transpiler (https://www.npmjs.com/package/pylang) and I tried it on exactly this benchmark (with some very minor changes):

PyLang: 0m19.386s (on node.js) about half as long as Python 3.11.0 native

The article uses bun, which uses Safari's JS runtime, which sometimes has significantly better performance than node.js for these sorts of benchmarks (especially for Webassembly), in my experience.

These tests were all on an M1 Macbook Pro.


It's due to the runtime.

JavaScript has a runtime that updates as it executes. It notices a loop is being run frequently and then compiles a new version of the loop that runs faster.

Python doesn't have this functionality.

WebAssembly is for running things in the browser and would slow everything down (Python, JS, C++). The tests in the post are run natively (on my command line).


Python does have this functionality, but it's hamstrung by the implementation.

Javascript is, for the most part, completely sandboxed and separate. Which means the JIT for Javascript is totally free to do neat things like move an object from one region of memory to another.

CPython, on the other hand, has to care about things like "was this object created in C? Is it pinned to that memory location? Does this method end up delegating to C?"

And you see that in the complexity it takes to introduce native methods in both. Moving and using data from JS to Webassembly is a PITA. Accessing data in a Python object from c ( and vice versa) is trivial.

That triviality is what gets in the way of JIT optimizations.


I don't buy this argument. WebAssembly is a vastly different beast than trivial C extensions (since WASM is a browser thing). JavaScript has both and is still much faster.

Bun (the runtime I used for the post), has a native C interface that's far simpler to use than Python (even with pybind/nanobind): https://github.com/oven-sh/bun#bunffi-foreign-functions-inte...

Deno (a v8-based runtime) has the same thing: https://deno.land/manual@v1.25.4/runtime/ffi_api

These use the standard C-ABI directly without requiring headers (unlike Python).

Node.js standardized a more Python-like C API called Node-API: https://nodejs.org/api/n-api.html

I believe Python can continue to add performance and is not hamstrung by the implementation.


The difference between all of these and how the python c api works is that 1 layer of abstraction and translation.

Take the deno example, in order to invoke a C function you have to tell deno "load this lib, this dynamic function is here, and the method signature looks like this".

Look at what types are permitted to be passed between the two as well. deno isn't exposing deno objects to C, it's exposing only simple primitive types. The cpython FFI, on the other hand grants C full access to python objects.

That one layer of abstraction and distance makes all the difference and is hard to backport in.

There's a reason alternative pythons (pypy, graalpy) don't really support it and struggle to support libs that rely heavily on it (like tensorflow).


You can expose objects. Here's how it is done in Bun: https://github.com/facebookresearch/shumai/blob/main/shumai/...

We've been using this feature heavily in Shumai.

I think you are vastly overestimating the complexity associated with this (user exposed ref-counting/garbage collection) and may not be totally up to date on what's implemented.


> I think you are vastly overestimating the complexity associated with this

No, I think you are misunderstanding the problem.

I'm not saying that, with a good 3rd party lib, generating FFI APIs can't be easy and slick. I'm saying that non-python languages have more complicated FFI APIs that practically necessitate these sorts of generation libraries (like bun).

When you grab a bit of memory out of an object in C with python, you are reaching directly into python's internal representation of the object and tickling the bits there.

When you do that with deno/javascript/others, there's a layer of abstraction introduced to keep our native method from directly tickling the bits that the VM is aware of.

That's the problem.

From C, you can create a new python object, store it off in a global variable, send it back to the python vm, and later in a thread go tickle some of the bits and see that tickling in the Python VM. Because that object is the same one used by Python and C.

The complication that arises with CPython is many very popular librarys (numpy, tensorflow, pandas) rely HEAVILY on the fact that the objects they are working with in C are the same ones python uses. That's why they've been so slow to port to pypy if at all.

And that ability for C libraries to very deeply interact with the VM is exactly the problem that makes it hard to improve CPython's JIT. That's the reason other python JITs, like pypy, either don't or have very limited support for CPython's FFI capabilities.

The reason FFI works so well with other languages is they drew very tight and clear boundaries around how interactions work and who owns what when.

So please, stop spamming "bun". It's a non sequitur.


I'll concede that Python has more nuanced bits of complexity exposed, but I suspect a willingness to break compatibility for the sake of performance would benefit the language a lot more than it would hurt it. I personally hope the hacker mentality and the immensely perf-driven nature of JavaScript is one day adopted by Python.

For those following, I want to make it clear that the "bits" being discussed are extremely nuanced and won't get in the way of doing obviously useful things in JavaScript.

Here's how you create a JS Object from C using Node-API: https://nodejs.org/api/n-api.html#napi_create_object

It conforms to ECMAScript 6.1.7 Object Types and is very much the same internal representation used by the VM.

Here's how one might tickle the internal representation used by Node.js (V8 in this case) to change the value of the underlying property of a JavaScript object from C: https://nodejs.org/api/n-api.html#napi_set_element


> I suspect a willingness to break compatibility for the sake of performance would benefit the language a lot more than it would hurt it

It took a long time for the community to recover from the python3 incompatibility with python2. I don't think we are ready for another round.


> but I suspect a willingness to break compatibility for the sake of performance would benefit the language a lot more than it would hurt it.

That's already happen in the form of graalpy and pypy. The community has voted and they want compatibility more than they want performance.


> Accessing data in a Python object from c ( and vice versa) is trivial. That triviality is what gets in the way of JIT optimizations.

Can't it be automated? Accessing data is often done in patterns that are very similar.

Perhaps we should access Python from Rust, would that help?


Absolutely, and it usually is. The FFI isn't generally hard to use because of the automation.


Just some extra info to add to this for OP/others. The keyword to Google to learn more about this would be a "JIT compiler". If you were to run each of those benchmarks to loop only a few times (or steps for the n-body example), say n=10, they would appear much slower because the JIT compiler needs lines of code to be run multiple times before it can begin making optimizations.

"pypy" is an alternative runtime for Python which can do JIT optimizations like Javascript interpreters do. Another commenter in a different subthread already posted a quick speed comparison involving pypy, it's much faster than the standard python runtime (called CPython).

But pypy isn't necessarily a synonym for Python (which almost always means CPython). It doesn't have 100% compatibility with 3rd party libraries (it's probably 99.9% but just 1 unsupported dependency is enough to prevent your whole project from going pypy).

And also with most Python projects, the python code isn't the slow part of the stack. If you're making a web app, your cache and database is probably where 99% of your request time is spent. For scientific computing, data analysis, or ai training, you're usually using 3rd party libraries written in a "fast" language like C/Fortran/Rust with python hooks, and your python just acts as glue code to pass data between these library calls.


> Is the performance difference of Python and Javascript due to the language or due to the runtime?

It's almost always the runtime. Unless the language spec makes it particularly difficult to implement a feature in a way that's performant while still respecting the spec.

Which is why the discussion on which languages are "faster" is seldom productive. Languages are not fast or slow by themselves. And, even when they are on average, implementing stuff on Assembly is no guarantee of speed. Quite often, higher level abstractions lead to optimizations that are difficult to replicate if one is operating at a lower level.

As a very trivial example, consider short circuit evaluation. It's something that you explicitly have to code in assembly, but most languages have implemented out of the box. Or garbage collection - allocating and deallocating memory on demand is memory efficient, but it's not necessarily what you want to do for performance. In many cases, deferring garbage collection allows for often-accessed memory to be retained, where as a C implementation would be constantly allocating and freeing. Fixing that requires programmer effort.

What I think matters the most is the ability of a language (+runtime) to access lower layers when needed (ASM blocks in C, FFI in Python).


>And, even when they are on average, implementing stuff on Assembly is no guarantee of speed. Quite often, higher level abstractions lead to optimizations that are difficult to replicate if one is operating at a lower level.

When browsing Code Golf, most often than not, the fastest implementation is in assembler. And sometimes it can be 2x - 4x faster than next fastest implementation which usually is C++.


While with enough trust even pigs can fly (witness the heroic efforts to make JS fast), some languages are indeed inherently faster than others either because the semantics are such that they are easier to translate to an actual machine efficiently or there is less abstraction between the language and the machine.


It's both. Language semantics make it hard to optimize Python, even for the runtime / even for a JIT.


An underrated factor in this conversation is implementation effort. Javascript is arguably the most widely run interpreted language in the world. Pre-Chrome, JS was uniformly rather slow, but Google was betting on webapps being good and usable, so they brought on some top-tier compilation/VM talent to build V8. As fast Javascript existed, it made it possible to do more natively in the browser, and other browser vendors joined in an arms race there. Tons of effort overall, tons of money and time from top-tier experts.

There was no equivalent competition between major organizations to make Python fast. Folks have tried, but not on the same scale. Yes, factors like language design and existing ecosystems entanglements played a role here, but if Python had the investment in performance Javascript had (financial and expert attention), it'd be dramatically faster today (though it may have required a fork or similar, depending on how tied to simplicity Guido was feeling).

To be clear, I'm not saying nobody tried to make Python faster, or that Python devs don't know what they're doing. I know very well both of those things aren't true. But, the kind of sophistication required to make Python fast in absolute terms is very hard to build and maintain, and for various reasons nobody who has found Python to be too slow has found "invest in a bunch of person-years of VM engineer attention dedicated to performance work" to be their best path.


I think you are right, but there are signs of great effort - pypy, unladen swallow, and many other projects that still exist or do not.


Python and JS are basically the same language in terms of semantics.


> Python and JS are basically the same language in terms of semantics.

Python is dynamic class-based OO with multiple inheritance. JS is dynamic prototypical OO, though it has recently added convenience syntax implementing single-inheritance class-based OO on top of it.

They are not the same.


It's still redirecting to another object at runtime in both cases. The details are obviously not exactly identical but there's no great difference in the object model just because one is called a "class" and one is called a "prototype".


Python also has much stronger typing in to converting types of things willy nilly without being instructed to.


well, as long as both of them are descendant of modula 2 :)

but no. while both languages share some similarities (dynamically typed, some form of "objects" and "classes") there are a lot of differences, both at "language level" and at "implementation level"


What are some important differences do you think?


Lexical scope and first class functions in JavaScript as a descendant from Lisp.




Guidelines | FAQ | Lists | API | Security | Legal | Apply to YC | Contact

Search: