Python's Unflattering Performance Review
Description
A six-panel meme using the 'You are known for...' format from the TV show 'The Office.' In the left panels, Michael Scott is conducting an interview. In the right panels, Pam Beesly is labeled 'Python.' In the first row, Michael begins, 'You are known for...' and Pam (as Python) excitedly answers, 'easy to understand'. Michael looks unconvinced. In the second row, Pam tries again with a confident smile, 'usefulness in data science'. In the final row, Michael delivers the punchline with a critical expression: 'being slower than c'. Pam's face shifts to one of utter disappointment. The meme humorously highlights the core trade-off of Python: its high-level syntax and powerful libraries make it excellent for developer productivity and data science, but as an interpreted language, its raw performance is a well-known weak spot compared to compiled languages like C
Comments
228Comment deleted
Python: spend 10 minutes writing a script that takes 10 hours to run. C: spend 10 hours writing a script that takes 10 minutes to run
Python is so readable even the quants ship it to prod - right up until the latency SLA hits, then it magically morphs into 10k lines of hand-rolled C extensions nobody claims ownership of
Python developers explaining to management why their data pipeline takes 3 hours: 'But think of all the time we saved not writing semicolons and managing memory!'
Python developers will spend three days optimizing their data pipeline with Cython and multiprocessing, only to realize the real bottleneck was a single unindexed database query. But hey, at least the code is readable enough that the next person can figure that out in five minutes instead of five hours - which is more than you can say for that 'performance-optimized' C codebase from 2003 that nobody dares touch because the original author documented it exclusively in Hungarian notation and regret
Python’s performance strategy is simple: push the hot path into NumPy/Cython, then benchmark the 90% glue while debating the GIL
Python: 10x easier to write, 10x slower to run - until the SLOs force that C interop hotpath rewrite
Python: easy to read, dominant in data science, and blazingly fast - right after you hand the hot loop to NumPy’s C while the GIL holds your coffee
hmm Comment deleted
what a coincidence Comment deleted
memalloc Comment deleted
oh yea because everyone here steals stale memes from reddit, deal with it Comment deleted
idk I'm not finding it on reddit, must've cone from a different platform Comment deleted
well the other platform stole from reddit cuz it's a highly upvoted post on r/programminghumor Comment deleted
is it? I can't find it Comment deleted
probably already taken down by the jannies, anyway I saw it yesterday Comment deleted
nope, found it https://www.reddit.com/r/ProgrammerHumor/comments/p2fgpb/you_evil_people/ Comment deleted
damn, respect Comment deleted
I looked both in hot and top last week, but couldn't find it Comment deleted
Lol C is not even slow compared to other object oriented languages Comment deleted
not at all, no Comment deleted
Yeah its only slow if you compare it to asm but asm cant be compared to object oriented language Comment deleted
rust is also slightly faster technically but really how fast your code runs comes down to how well you write it Comment deleted
Well the overhead is the major thing that can be compared between stuff Comment deleted
aye, Rust has 0 cost abstractions fyi 🙃 Comment deleted
That sounds pretty good Comment deleted
C is not slow even compared to asm.. it all depends on how you write your code and how you compile it. If you build the project with the instrumented profiling stage first, I doubt you’d be able to write it faster in asm, provided you have the same skills in both languages. And if you target multiple CPU models (not even talking about different brands of the same arch, like Intel & AMD on amd64) I bet you won’t be able to outperform a well built C with asm, at least if the project is not a basic algo and you don’t have an infinite time to code it Comment deleted
C still has overhead, compared to asm it's always slower Comment deleted
> It’s always slower No, it isn’t.. I was working in a HPC (high-performance computing) environment for 10 years, coding in C and asm by hand. You should know your CPU so so well to be able to code something in asm faster than a well-built C, that it’s almost impossible Comment deleted
I mean perfect asm will always be better than perfect C obviously abstractions help massively with programming effectively Comment deleted
Just make sure not to run this: {} - [] Comment deleted
not sure what that does Comment deleted
Because the opertor - automatically casts everything to numbers, and an empty array is false when converted to booleans and any object is a true if converted to booleans it will completely logically subtract 1 - 0 what then equals perfectly logically: -0 Comment deleted
so {}-[] is the same as -0, yes? Comment deleted
Yes Comment deleted
why negative zero? Comment deleted
Best of all is I can clearly remember that in school we were taught that 0 has no sign at all Comment deleted
same until we got to Mengenlehre (group theory I think?) where it's not in N+, but in N+0 (positive Natural numbers, positive Natural numbers and 0) Comment deleted
Lol Comment deleted
Ask the IEEE 754 standard Comment deleted
but that's only for floats, no? Comment deleted
nvm it's js I remember Comment deleted
Yeah🤡 Comment deleted
glad I can use rust/wasm now, so I can decide between using a string, a string pointer or a string object which all have wildly different use cases Comment deleted
Well C++ you can choose that too PLUS you can do it in w_char (unicode) or as normal ascii Comment deleted
No.. Comment deleted
mate C literally compiles to asm, it can't be faster than that Comment deleted
I guess you don’t get the point.. the fact that C compiles to the same binary code that asm doesn’t make it slower.. to write proper asm code you should understand so much of the underlying CPU functioning, that today you can’t outsmart the compilers.. Comment deleted
I already told you, I'm not talking about practical performance. Ofc abstractions make it easier for the programmer to write good programs because there's a shitton of optimizations that can be applied to code that's often used, but are complicated as hell. Comment deleted
Then what are you talking about? 🤔 Comment deleted
reading comprehension. I already told you at least twice Comment deleted
You mean a theoretical abstract performance completely detached from the real world specifics? Like infinite knowledge and infinite coding time? Comment deleted
Is this sarcasm I dont get it exactly Comment deleted
This would be funnier if it would say "being slower that JavaScript" but idk if this would actually be true Comment deleted
comes down to the task - technically, pure js is faster because it's been optimized to death, but python has heaps of built-in compiled libraries Comment deleted
Lol js is JIT compiled thats a huge overhead lol Comment deleted
And the best thing is that Rust can outperform C the same way C can outperform asm Comment deleted
rust has less overhead than C, which makes it faster Comment deleted
Rust has C as the backend.. what do you mean? 🤔 Comment deleted
no it doesn't lol Comment deleted
it compiles to asm just like any other compiled lang Comment deleted
afaik it compiles to llvm and then to asm Comment deleted
asm is the end product, anyway, I don't know much about the intermediate process Comment deleted
Asm is not the end product.. Comment deleted
asm as in the byte code, not the readable code Comment deleted
But asm has numerous stages to be “converted” to bytecode Comment deleted
it's a direct analogue to what's in the bytecode Comment deleted
Yepp Comment deleted
And it’s not the matter of overhead in instructions.. if you have a L2 cache miss, it’s like 100 times (or even more) slower.. Comment deleted
The nowadays compilers have much more information about what code does than the coder, except some simple algo. Have you tried instrumented profiling? Comment deleted
> perfect asm read my message again. I was talking about theoretical maximum performance Comment deleted
Ok, but I’m talking about real projects.. in real life it’s almost impossible to outperform well-written C built with profiling Comment deleted
nobody argued with that Comment deleted
There’s just no way you can have that amount of info to incorporate all the knowledge about real execution paths on real data that the compiler would gather Comment deleted
The abstract theory is another question.. Comment deleted
With LLVM it compiles to an intermediate representation which would then have additional optimization stages. Same as if you write it in C with Clang Comment deleted
Great discussion 😊 Comment deleted
sorry, I'll stop Comment deleted
It’s just closer to bare-metal binary code than C, but that’s it, it’s not the binary code Comment deleted
It’s close enough I’d say.. also, the way it executes inside of the CPU is yet another question/issue.. Comment deleted
anyway, end of discussion, this won't lead us anywhere Comment deleted
At least for me, this was very interesting. I wish I had teachers like Anatoli and you in college, discussing different points of view on the same subject. Comment deleted
ah? well, I'm glad you liked the discussion, but I sure didn't :/ I often get myself into these debates despite not liking them. Comment deleted
cause I'm stubborn and dislike being wrong, which is an unfortunate combo to have as an introvert Comment deleted
In this case yes, C code would be slower than asm code Comment deleted
But I’m not sure why would someone consider that Comment deleted
just for fun - it's like comparing the top speed of cars. In the real world, they're never gonna reach that speed, but it's fun to compare them Comment deleted
Ok, for that case I agree. But if we see the practical performance aspects of different languages, then IMO Rust is the fastest one, then goes C and then asm. The constraints here are the limited knowledge of.. say.. a team of senior devs and a limited time they have to develop a real-world piece of software that works on real-world data supplied to it by real-world users.. Comment deleted
agreed, that definitely makes sense, although I think it's important to note that Rust is a lot newer than C which means there are less existing frameworks, which may hinder development. In any way C and Rust are very different languages, and I don't like to compare them directly in the context of real-world scenarios because I'm really not knowledgeable enough to do that. Comment deleted
😏 I guess today there’s much more tooling and really reusable libs in Rust than ever in the history of C. There was no single time that I couldn’t find some lib for a generic functionality like JSON parsing or a HTTPstand-alone server in https://lib.rs. In C the largest problem always was the lack of reusable (in practice) libs. The Rust crates is a godsent aspect of the language for C devs Comment deleted
Hahaha. Don't take it personal, you are talking about theoretical programmers programming on theoretical processors. It doesn't get less personal than that. Also, I've seen Anatoli giving lectures in conventions, and he knows a shit-ton. It's always interesting challenging a knowledgeable person and understanding their reasoning. Comment deleted
The point here is that good C compliers like modern CLang/LLVM and gcc have more knowledge about the underlying hardware incorporated in them that a typical senior devs team would have. So it (the compiler) has a better understanding of how to translate the developers intentions into the bytecode, taking into account the cache sizes, the number of CPU pipelines and how the instructions are getting processed inside the CPU, to avoid stale pipelines, cache misses, etc that compared to say 2-3 additional asm instructions are 3-4 orders of magnitude bigger issues.. Comment deleted
And if you perform an instrumented profiling of your C code, where the compiler instruments your code in order to collect the real-data execution paths metrics and then execute the binary on real-world data to collect the metrics, then recompile it feeding the compiler this statistics, that it would know about such situations that you couldn’t even suppose can happen. In this case it would be able to optimize the code even better, and except some really simple code like 10 lines of C, you won’t be able to outsmart the compiler Comment deleted
didn't even know compilers could take metrics into account, you learn something new every day I guess Comment deleted
https://en.m.wikipedia.org/wiki/Profile-guided_optimization Comment deleted
thanks for the link, I'll bookmark it to read tomorrow morning Comment deleted
I was working in HPC projects asm hand-coding for about 8 years (around 2004), for IBM BlueGene/L supercomputer and x86 megaclusters of 500+ nodes.. was holding the details of almost the entire Intel Instructions Set in brain.. but then SSE instructions appeared, caches got bigger, more CPU pipelining and smarter CPU tricks to execute the instructions.. so to outsmart the compiler with profile-guided optimizations became a task not worth the effort.. say 5% faster spending months on performance tuning the project.. and then the compilers became even smarter, the CPUs even more complex so I quit the asm performance tuning as it made no sense Comment deleted
ah, see, I didn't have time in 2004 as I was busy being born lol Respect, man, sounds like you're an elite professional. Comment deleted
😆 thanks Comment deleted
damn I can't keep up with your walls of text 😅 Comment deleted
basically it's not all that clear now Comment deleted
to get good perf you actually have to use quite alot of unsafe, risking ub Comment deleted
also, compared to c++ rust's meta sucks Comment deleted
Why so? Do you have a link to read about the details? Comment deleted
consider this example: suppose you have a function that fails very rarely, the rust way is to make that function return a Result, but that actually incurs a cost on the happy path, bc you have to have an if even if your Result is Ok, on the other hand, if you user exceptions, you can avoid that if and save time on the happy path Comment deleted
also there are times where you have to perform runtime checks to ensure safety, bc rust's type system sucks, whereas in c++ you could just make it not compile Comment deleted
But exceptions are not free either Comment deleted
yeah, it's a tradeoff Comment deleted
better to have both Comment deleted
But the “if” is just a single tick if you guess the result, do you know that? Comment deleted
i know that, but it's worse than none Comment deleted
Also a single tick doesn’t matter at all if it’s not inside some absolutely EPIC cycle, and you’re talking about saving it on func exit, that implies a number of stack reads, which could result in cache misses, so your tick is just lost in the cache misses delays and pipeline stales causing hundreds of ticks delay.. Comment deleted
the question is when Comment deleted
👆you say that the Rust way to check the result is inefficient and you should recure to unsafe Rust to "fix" it Comment deleted
nope, that's not at all what i told Comment deleted
i told that you want both options Comment deleted
With respect to Rust outperforming C, the point here is that the Rust compiler has much more knowledge about your intentions that if you write the code in C, so it can make assumptions the C compiler can’t. And with these assumptions it can optimize it even further. On the other hand, it’s much easier to write performant code in Rust than in C, not to say that in C you just never have time for the optimizations as your are constantly debugging it investigating strange crushes, especially when multithreading and coding non-trivial stuff Comment deleted
So in practical terms your real-world Rust code is almost guaranteed to outperform your real-world C code Comment deleted
If you haven’t seen it yet, I suggest you check this short online book about Rust performance tuning: https://nnethercote.github.io/perf-book/ Comment deleted
😄 yeah Rust was godsent to the C devs that were suffering for decades.. Comment deleted
now life is great again Comment deleted
I mean, under no circumstances would I make my code that ugly to save a single tick! Comment deleted
as of today, you don't have an option Comment deleted
even if you liked to Comment deleted
If you have a single cache miss its like 150 ticks Comment deleted
If you stale the pipeline with some unordered instruction it could lead to multiple cache misses Comment deleted
I just trust the compiler to handle all that stuff.. you never win by saving a tick.. been there, quit it Comment deleted
it's a small example that i thought up off the top of my head Comment deleted
btw, on the topic of error handling, tho it's more about expressiveness and not perf: why isn't there a std::variant analog in rust? you literally can't live without it in result based error handling world Comment deleted
I mean this is not the way you optimize your code Comment deleted
The 2 most critical performance issues on the CPU level are the cache misses and the pipeline stales Comment deleted
with exceptions, when do they happen? Comment deleted
they happen only on the sad path, not on the happy one Comment deleted
Have you ever coded in asm? Comment deleted
yes Comment deleted
Then what do you mean about the things happening on a happy path and not happening in the sad path? Comment deleted
when exception is/is not thrown Comment deleted
How do you implement a condition check in asm? Comment deleted
with a conditional jump (eg. jne), cmov may actually be an option sometimes too Comment deleted
If the result is in eax and 0 means no errors, the code would be: test eax, eax jnz error_handling Comment deleted
yes, callee side, but caller side ou don't have to do anything Comment deleted
oh fuck, a test, not a branch Comment deleted
there is a test instruction, and arithmetic ops write to the flag register too Comment deleted
https://gcc.godbolt.org/z/Thh8YcWaT an example Comment deleted
Exceptions are on the much higher level, in the C code.. but as you’re talking about saving a single if which is a conditional jump in asm like (je/jz) which’s is 1 tick (CPU cycle) on right guess and 3 ticks on wrong guess (right means you specify it in such a way that most of the times the jump is not performed), I’m showing you the underlying considerations worth much more than trying to save a single if. Comment deleted
And as it was pointed before, exceptions are basically the same ifs, but more complex on the asm instructions level, so it’s not clear what would be faster.. a simple je (if in asm) or some non-trivial exceptions checking Comment deleted
Check here for relative latenixes of cache misses: https://stackoverflow.com/questions/1126529/what-is-the-cost-of-an-l1-cache-miss#29188516 Comment deleted
oh, i remembered one more thing: allocating a large object on the heap involves memcpying it off the stack first Comment deleted
well, all objects, but this matters for the particularly large ones Comment deleted
Are you sure you fully understand this stuff? Comment deleted
well, unless you actually intend on handling the error Comment deleted
You always have to make a check.. if not, you can’t know if you have an error condition or not Comment deleted
look at the example Comment deleted
So compare the code you suggest of an exception: thrw(bool) [clone .cold]: .L3: mov edi, 4 call __cxa_allocate_exception xor edx, edx mov esi, OFFSET FLAT:_ZTIi mov DWORD PTR [rax], 43 mov rdi, rax call __cxa_throw with a result check code: test eax, eax jnz error_handling and tell me what's faster Comment deleted
do you see the cold Comment deleted
didn't i already tell you that it's efficient only when errors are rare? Comment deleted
you can even turn off the optimizations and see that there is a call and then an immediate leave Comment deleted
well, actually, afaik rust already uses this mechanism for unwind panics Comment deleted
unwind panics are entirely different stuff that what was your initial suggestion for optimization Comment deleted
they are the same mechanism Comment deleted
same mechanism as the C exceptions, but you proposed another thing initially Comment deleted
c exceptions? Comment deleted
And I'm saying that a simple result check is just 1 tick of the CPU time Comment deleted
a simple results check is the least expensive option of all Comment deleted
you simply refuse to see that sth like ? operator is free as in no if with exceptions Comment deleted
🤔 could you please provide the asm code for both cases? I guess I don't understand what you mean.. Comment deleted
https://gcc.godbolt.org/z/TYWMEKPco here, with one more fnction Comment deleted
look at thrw2, see no if Comment deleted
what do you mean no if? I don't understand what you propose.. i.e. there is a func in rust that returns something as a Result and you should check it for errors in the outer function.. this would be as simple as: test eax, eax jnz error_handling Comment deleted
https://gcc.godbolt.org/z/K3zvMKn6n Comment deleted
wait no, it's not good yet Comment deleted
no RAM involved, no cache misses Comment deleted
then you suggest a huge and complex code with function calls that involve writing to RAM invalidating your caches, which implies cache misses, etc.. and you're saying it's faster? don't understand your point Comment deleted
it's only in rare cases, i repeat Comment deleted
look at res2 now Comment deleted
do you mean this code is fast? Comment deleted
https://gcc.godbolt.org/z/1118bMj9K added math to illustrate a point Comment deleted
and what would be the equivalent in Rust? Comment deleted
https://gcc.godbolt.org/z/M54rzeMnr Comment deleted
now take a look at res2 vs thrw2 Comment deleted
also take a look at that cmov, which will never be just 1 tick Comment deleted
add there a main, call there the necessary func and let's see the total cost of execution in both cases Comment deleted
well, i'll leave it as an excersise for you, as i am alre in bed Comment deleted
😆 thanks, done those exercises 25 years ago Comment deleted
write its alternative in Rust and compare Comment deleted
if you can't see the clear difference in favor of Rust from your own examples, the only thing that can help is if you calculate the total cost of execution of your examples and compare 2 numbers Comment deleted
ofc if the probability of that bool being false is 50% then ofc rust wins, but if it's more like 0.1% then you better bet c++ will win Comment deleted
just add main() to Rust, in both Rust and C put there a loop of say 1M iterations of calling the func you want to prove, then compile and run both binaries and see the result Comment deleted
run them this way: $ time rust_bin and $ time c_bin Comment deleted
that won't work tho Comment deleted
now tell me why just time won't work Comment deleted
you say it won't work, you say why it won't work Comment deleted
a question: does a program really start in main? Comment deleted
are you running a basic asm quiz? Comment deleted
no asm Comment deleted
you don't have to know it to answer Comment deleted
and answer why that won't work Comment deleted
i've already spent toooooo much time discussing with you that basic stuff.. if you want to prove your point, provide here the same code in both C and Rust that compiles and runs without fixing it.. and we'll see the results Comment deleted
now Comment deleted
what was the disputed claim? holy fuck Comment deleted
Funniest part was looking at this meme and seeing "197 comments" Comment deleted
take a look at theese srcs Comment deleted
need your confirmation fefore i reveal the numbers Comment deleted
using vec for a for loop.. Comment deleted
otherwise it's inlined away, and to be fair it's done in c++ and rust equally Comment deleted
this makes the program like 100 times (or more) slower Comment deleted
i don't have time right now, but just for fun, this weekend I'll send you a correctly coded rust code Comment deleted
Vec for loop is q is a construct known to be extremely slow compared yo a simple int loop Comment deleted
I don't have an int loop in c++ tho Comment deleted
Are you testing the Result handling performance or a vec enumeration performance?? Comment deleted
vec iteration is only there to prevent inlining, if you have a better idea be my guest Comment deleted
And BTW your code is extremely convoluted.. all the time counting code should not be there Comment deleted
are you sure you don't want to compare allocation performance? Comment deleted
90% of what you put in the cpp is not needed Comment deleted
You claimed that the Result check is slower than exceptions Comment deleted
So let’s check your claim first Comment deleted
Then you can make a new claim and we’ll see if you’re right or not Comment deleted
The thing is simple: you make a for loop with ints in both cases and put there the check Comment deleted
this is also compiled away Comment deleted
It doesn’t matter how the loop ends implemented in bytecode, we are not testing the loop, it’s just to repeat the same check many times as a single check won’t show the difference Comment deleted
it caches the result and compiles away the loop entierly Comment deleted
And it also doesn’t matter the inlining.. we are testing the default compilation without anything special about it.. cc x.cpp and rustc main.rs Comment deleted
so no optimizations? Comment deleted
Exactly, no special optimizations (but the code do should be well written, i.e. with known good constructs), no pragmas, inlines & co, just the basic code of try/catch vs Result check and a basic int loop for enough iterations for the execution to last for some 5 seconds to make the process start statistically negligible Comment deleted
did you try to read unoptimized rust? Comment deleted
The most simple code in both cases Comment deleted
Should be like 10 lines of code at most Comment deleted
So when you measure the time with time the measurement is just of the loop itself Comment deleted
rust 0.38s user 0.00s system 99% cpu 0.385 total cpp 0.04s user 0.00s system 99% cpu 0.048 total which is bs bc why the fuck would you want all optimizations off in the first place Comment deleted