5 ms·
The primary focus here is good and something I hadn't considered: python memory being so dynamic leads to poor cache locality. Makes sense. I will leave that to
by nu11ptr 1y ago
The primary focus here is good and something I hadn't considered: python memory being so dynamic leads to poor cache locality. Makes sense. I will leave that to others to dig into.
That aside, I was expecting some level of a pedantic argument, and wasn't disappointed by this one:
"A compiler for C/C++/Rust could turn that kind of expression into three operations: load the value of x, multiply it by two, and then store the result. In Python, however, there is a long list of operations that have to be performed, starting with finding the type of p, calling its __getattribute__() method, through unboxing p.x and 2, to finally boxing the result, which requires memory allocation. None of that is dependent on whether Python is interpreted or not, those steps are required based on the language semantics."
The problem with this argument is the user isn't trying to do these things, they are trying to do multiplication, so the fact that the lang. has to do all things things in the end DOES mean it is slow. Why? Because if these things weren't done, the end result could still be achieved. They are pure overhead, for no value in this situation. Iow, if Python had a sufficiently intelligent compiler/JIT, these things could be optimized away (in this use case, but certainly not all). The argument is akin to: "Python isn't slow, it is just doing a lot of work". That might be true, but you can't leave it there. You have to ask if this work has value, and in this case, it does not.
By the same argument, someone could say that any interpreted language that is highly optimized is "fast" because the interpreter itself is optimized. But again, this is the wrong way to think about this. You always have to start by asking "What is the user trying to do? And (in comparison to what is considered a fast language) is it fast to compute?". If the answer is "no", then the language isn't fast, even if it meets the expected objectives. Playing games with things like this is why users get confused on "fast" vs "slow" languages. Slow isn't inherently "bad", but call a spade a spade. In this case, I would say the proper way to talk about this is to say: "It has a fast interpreter". The last word tells any developer with sufficient experience what they need to know (since they understand statically compiled/JIT and interpreted languages are in different speed classes and shouldn't be directly compared for execution speed).
- andylei 1y agoThe previous paragraph is > Another "myth" is that Python is slow because it is interpreted; again, there is some truth to that, but interpretation is only a small part of what makes Python slow. He concedes its slow, he's just saying it's not related to how interpreted it is.
- nu11ptr 1y agoI would argue this isn't true. It is a big part of what makes it slow. The fastest interpreted languages are one to two orders of magnitude slower than for example C/C++/Rust. If your language does math 20-100 times slower than C, it isn't fast from a user perspective. Full stop. It might, however, have a "fast interpreter". Remember, the user doesn't care if it is a fast for an interpreted language, they are just trying to obtain their objective (aka do math as fast as possible). They can get cache locality perfect, and Python would still be very slow (from a math/computation perspective).
- nyrikki 1y agoThe 200-100 times slower is a bit cherry picked, but use case does matter. Typically from a user perspective, the initial starting time is either manageable or imperceptible in the cases of long running services, although there are other costs. If you look at examples that make the above claim, they are almost always tiny toy programs where the cost of producing byte/machine code isn't easily amortized. This quote from the post is an oversimplification too: > But the program will then run into Amdahl's law, which says that the improvement for optimizing one part of the code is limited by the time spent in the now-optimized code I am a huge fan of Amdahl's law, but also realize it is pessimistic and most realistic with parallelization. It runs into serious issues when you are multiprocessing vs parallel processing due to preemption, etc . Yes you still have the costs of abstractions etc...but in today's world, zero pages on AMD, 16k pages and a large number of mapped registers on arm, barrel shifters etc... make that much more complicated especially with C being forced into trampolines etc... If you actually trace the CPU operations, the actual operations for 'math' are very similar. That said modern compilers are a true wonder. Interpreted language are often all that is necessary and sufficient. Especially when you have Internet, database and other aspects of the system that also restrict the benefits of the speedups due to...Amdahl's law.
- nu11ptr 1y agoI'm not so much cherry picking as I am specifically talking compute (not I/O,stdlib) performance. However, when measured for general purpose tasks, that would involve compute and things like I/O, stdlib performance, etc., Python on the whole is typically NOT 20-100x times slower for a given task. Its I/O layer is written in C like many other languages, so the moment you are waiting on I/O you have leveled the playing field. Likewise, Python has a very fast dict implementation in C, so when doing heavy map work, you also amortorize the time between the (brutally slow) compute and the very fast maps. In summary, it depends. I am talking about compute performance, not I/O or general purpose task benchmarking. Yes, if you have a mix of compute and I/O (which admittedly is a typical use case), it isn't going to be 20-100x slower, but more likely "only" 3-20x slower. If it is nearly 100% I/O bound, it might not be any slower at all (or even faster if properly buffered). If you are doing number crunching (w/o a C lib like NumPy), your program will likely be 40-100x slower than doing it in C, and many of these aren't toy programs.
- ActivePattern 1y agoA “sufficiently smart compiler” can’t legally skip Python’s semantics. In Python, p.x * 2 means dynamic lookup, possible descriptors, big-int overflow checks, etc. A compiler can drop that only if it proves they don’t matter or speculates and adds guards—which is still overhead. That’s why Python is slower on scalar hot loops: not because it’s interpreted, but because its dynamic contract must be honored.
- pjmlp 1y agoIn Smalltalk, p x * 2 has that flow that as well, and even worse, lets assume the value returned by p x message selector, does not understand the * message, thus it will break into the debugger, then the developer will add the * message to the object via the code browser, hit save, and exit the debugger with redo, thus ending the execution with success. Somehow Smalltalk JIT compilers handle it without major issues.
- ActivePattern 1y agoSmalltalk JITs make p x * 2 fast by speculating on types and inserting guards, not by skipping semantics. Python JITs do the same (e.g. PyPy), but Python’s dynamic features (like __getattribute__, unbounded ints, C-API hooks) make that harder and costlier to optimize away. You get real speed in Python by narrowing the semantics (e.g. via NumPy, Numba, or Cython) not by hoping the compiler outsmarts the language.
- pjmlp 1y agoPeople keep forgetting about image based semantics development, debugger, meta-classes, messages like becomes:,... There is to say everything dynamic that can be used as Python excuse, Smalltalk and Self, have it, and double up.
- tekknolagi 1y agoIf I may toot my own horn: https://bernsteinbear.com/blog/typed-python/ https://bernsteinbear.com/blog/typed-python/
- 1y ago
- rstuart4133 1y ago> The problem with this argument is the user isn't trying to do these things, I'd argue differently. I'd say the the problem isn't that the user is doing those things, it's that the language doesn't know what he's trying to do. Python's explicit goal was always ergonomics, and it was always ergonomics over speed or annoying compile time error messages. "Just run the code as written dammit" was always the goal. I remember when the never class model was introduced, necessitating the introduction of __get_attribute__. My first reaction as a C programmer was "gee you took a speed hit there". A later reaction was to use it to twist the new system into something it's inventors possibly never thought of. It was a LR(1) parser, that let you write the grammars as regular Python statements. While they may not have thought abusing the language in that particular way, I'm sure the explicit goal was to create a framework that any idea to be expressed with minimal code. Others also used to hooks they provided into the way the language builds to create things like pydantic and spyne. Spyne for example lets you express the on-the-wire serialisation formats used by RPC as Python class declarations, and then compile them into JSON, xml, SOAP of whatever. Sqlalchamey lets you express SQL using Python syntax, although in a more straightforward way. All of them are very clever in how they twist the language. Inside those frameworks, "a = b + c" does not mean "add b to c, and place the result in a". In the LR(1) parser for example it means "there is a production called 'a', that is a 'b' followed by a 'c'". 'a' in that formulation holds references to 'b' and 'c'. Later the LR(1) parser will consume that, compiling it into something very different. The result is a long way from two's compliment addition. It is possible to use a power powerful type systems in a similar way. For example I've seen FPGA designs expressed in Scalar. However, because Scalar's type system insists on knowing what is going on at compile time, Scalar had a fair idea of what the programmer is building. The compile result isn't going to be much slower than any other code. Python achieved the same flexibility by abandoning type checking at compile time almost entirely, pushing it all to run time. Thus the compiler has no idea of what going to executed in the end (the + operation in the LR parser only gets executed once for example), which is what I said above "it's that the language doesn't know what the programmer is trying to do". You argue that since it's an interpreted language, it's the interpreters jobs to figure out what the programmer is trying to do at run time. Surely it can figure out that "a = b + c" really is adding two 32 bit integers that won't overflow. That's true, but that creates a low of work to do at run time. Which is a round about way of saying the same thing as the talk: electing to do it at run time means the language chose flexibility over speed. You can't always fix this in an interpreter. Javascript has some of the best interpreters around, and they do make the happy path run quickly. But those interpreters come with caveats, usually of the form "if you muck around with the internals of classes, by say replacing function definitions at run time, we abandon all attempts to JIT it". People don't typically do such things in Javascript, but as it happens, Python's design with it's meta classes, dynamic types created with "type(...)", and "__new__(..)" almost could be said encourage that coding style. That is, again, a language design choice, and it's one that favours flexibility over speed.
- dragonwriter 1y ago> They are pure overhead, for no value in this situation. Iow, if Python had a sufficiently intelligent compiler/JIT, these things could be optimized away (in this use case, but certainly not all). Hence, Numba.