[HN Gopher] Python numbers every programmer should know
___________________________________________________________________
Python numbers every programmer should know
Author : WoodenChair
Score : 237 points
Date : 2026-01-01 14:39 UTC (8 hours ago)
(HTM) web link (mkennedy.codes)
(TXT) w3m dump (mkennedy.codes)
| ktpsns wrote:
| Nice numbers and it's always worth to know an order of magnitude.
| But these charts are far away from what "every programmer should
| know".
| jerf wrote:
| I think we can safely steelman the claim to "every Python
| programmer should know", and even from there, every "serious"
| Python programmer, writing Python professionally for some
| "important" reason, not just everyone who picks up Python for
| some scripting task. Obviously there's not much reason for a C#
| programmer to go try to memorize all these numbers.
|
| Though IMHO it suffices just to know that "Python is 40-50x
| slower than C and is bad at using multiple CPUs" is not just
| some sort of anti-Python propaganda from haters, but a fairly
| reasonable engineering estimate. If you know that you don't
| really need that chart. If your task can tolerate that sort of
| performance, you're fine; if not, figure out early how you are
| going to solve that problem, be it through the several ways of
| binding faster code to Python, using PyPy, or by not using
| Python in the first place, whatever is appropriate for your use
| case.
| woodruffw wrote:
| Great reference overall, but some of these will diverge in
| practice: 141 bytes for a 100 char string won't hold for non-
| ASCII strings for example, and will change if/when the object
| header overhead changes.
| tgv wrote:
| I doubt list and string concatenation operate in constant time,
| or else they affect another benchmark. E.g., you can concatenate
| two lists in the same time, regardless of their size, but at the
| cost of slower access to the second one (or both).
|
| More contentiously: don't fret too much over performance in
| Python. It's a slow language (except for some external libraries,
| but that's not the point of the OP).
| jerf wrote:
| String concatenation is mentioned twice on that page, with the
| same time given. The first time it has a parenthetical
| "(small)", the second time doesn't have it. I expect you were
| looking at the second one when you typed that as I would agree
| that you can't just label it as a constant time, but they do
| seem to have meant concatenating "small" strings, where the
| overhead of Python's object construction would dominate the
| cost of the construction of the combined string.
| fooker wrote:
| Counterintuitively: program in python only if you can get away
| without knowing these numbers.
|
| When this starts to matter, python stops being the right tool for
| the job.
| MontyCarloHall wrote:
| Exactly. If you're working on an application where these
| numbers matter, Python is far too high-level a language to
| actually be able to optimize them.
| libraryofbabel wrote:
| Or keep your Python scaffolding, but push the performance-
| critical bits down into a C or Rust extension, like numpy,
| pandas, PyTorch and the rest all do.
|
| But I agree with the spirit of what you wrote - these numbers
| are interesting but aren't worth memorizing. Instead,
| instrument your code in production to see where it's slow in
| the real world with real user data (premature optimization is
| the root of all evil etc), profile your code (with pyspy, it's
| the best tool for this if you're looking for cpu-hogging code),
| and if you find yourself worrying about how long it takes to
| add something to a list in Python you really shouldn't be doing
| that operation in Python at all.
| eichin wrote:
| "if you're not measuring, you're not optimizing"
| Demiurge wrote:
| I agree. I've been living off Python for 20 years and have
| never needed to know any of these numbers, nor do I need them
| now, for my work, contrary to the title. I also regularly use
| profiling for performance optimization and opt for Cython,
| SWIG, JIT libraries, or other tools as needed. None of these
| numbers would ever factor into my decision-making.
| AtlasBarfed wrote:
| .....
|
| You don't see any value in knowing that numbers?
| TuringTest wrote:
| As others have pointed out, Python is better used in places
| where those numbers aren't relevant.
|
| If they start becoming relevant, it's usually a sign that
| you're using the language in a domain where a duck-typed
| bytecode scripting-glue language is not well-suited.
| Quothling wrote:
| Why? I've build some massive analytic data flows in Python with
| turbodbc + pandas which are basically C++ fast. It uses more
| memory which supports your point, but on the flip-side we're
| talking $5-10 extra cost a year. It could frankly be $20k a
| year and still be cheaper than staffing more people like me to
| maintain these things, rather than having a couple of us and
| then letting the BI people use the tools we provide for them.
| Similarily when we do embeded work, micro-python is just so
| much easier to deal with for our engineering staff.
|
| The interoperability between C and Python makes it great, and
| you need to know these numbers on Python to know when to
| actually build something in C. With Zig getting really great
| interoperability, things are looking better than ever.
|
| Not that you're wrong as such. I wouldn't use Python to run an
| airplane, but I really don't see why you wouldn't care about
| the resources just because you're working with an interpreted
| or GC language.
| fooker wrote:
| > you need to know these numbers on Python to know when to
| actually build something in C
|
| People usually approach this the other way, use something
| like pandas or numpy from the beginning if it solves your
| problem. Do not write matrix multiplications or joins in
| python at all.
|
| If there is no library that solves your problem, it's a great
| indication that you should avoid python. Unless you are
| willing to spend 5 man-years writing a C or C++ library with
| good python interop.
| oivey wrote:
| People generally aren't rolling their own matmuls or joins
| or whatever in production code. There are tons of tools
| like Numba, Jax, Triton, etc that you can use to write very
| fast code for new, novel, and unsolved problems. The idea
| that "if you need fast code, don't write Python" has been
| totally obsolete for over a decade.
| fooker wrote:
| Yes, that's what I said.
|
| If you are writing performance sensitive code that is not
| covered by a popular Python library, don't do it unless
| you are a megacorp that can put a team to write and
| maintain a library.
| oivey wrote:
| It isn't what you said. If you want, you can write your
| own matmul in Numba and it will be roughly as fast as
| similar C code. You shouldn't, of course, for the same
| reason handrolling your own matmuls in C is stupid.
|
| Many problems can performantly solved in pure Python,
| especially via the growing set of tools like the JIT
| libraries I cited. Even more will be solvable when things
| like free threaded Python land. It will be a minority of
| problems that can't be, if it isn't already.
| its-summertime wrote:
| From the complete opposite side, I've built some tiny bits of
| near irrelevant code where python has been unacceptable, e.g.
| in shell startup / in bash's PROMPT_COMMAND, etc. It ends up
| having a very painfully obvious startup time, even if the
| code is nearing the equivalent of Hello World
| time python -I -c 'print("Hello World")' real
| 0m0.014s time bash --noprofile -c 'echo "Hello
| World"' real 0m0.001s
| dekhn wrote:
| What exactly do you need 1ms instead of 14ms startup time
| in a shell startup? The difference is barely perceptible.
|
| Most of the time starting up is time spent seartching the
| filesystem for thousands of packages.
| NekkoDroid wrote:
| > What exactly do you need 1ms instead of 14ms startup
| time in a shell startup?
|
| I think as they said: when dynamically building a shell
| input prompt it starts to become very noticable if you
| have like 3 or more of these and you use the terminal a
| lot.
| bathtub365 wrote:
| These basically seem like numbers of last resort. After you've
| profiled and ruled out all of the usual culprits (big disk
| reads, network latency, polynomial or exponential time
| algorithms, wasteful overbuilt data structures, etc) and need
| to optimize at the level of individual operations.
| zelphirkalt wrote:
| I doubt there is much to gain from knowing how much memory an
| empty string takes. The article or the listed numbers have a
| weird fixation on memory usage numbers and concrete time
| measurements. What is way more important to "every programmer" is
| time and space complexity, in order to avoid designing
| unnecessarily slow or memory hungry programs. Under the
| assumption of using Python, what is the use of knowing that your
| int takes 28 bytes? In the end you will have to determine,
| whether the program you wrote meats the performance criteria you
| have and if it does not, then you need a smarter algorithm or way
| of dealing with data. It helps very little to know that your
| 2d-array of 1000x1000 bools is so and so big. What helps is
| knowing, whether it is too much and maybe you should switch to
| using a large integer and a bitboard approach. Or switch
| language.
| Qem wrote:
| > Under the assumption of using Python, what is the use of
| knowing that your int takes 28 bytes?
|
| Relevant if your problem demands instatiation of a large number
| of objects. This reminds me of a post where Eric Raymond
| discusses the problems he faced while trying to use Reposurgeon
| to migrate GCC. See http://esr.ibiblio.org/?p=8161
| kingstnap wrote:
| I disagree. Performance is a leaky abstraction that *ALWAYS*
| matters.
|
| Your cognition of it is either implicit or explicit.
|
| Even if you didn't know for example that list appends was
| linear and not quadratic and fairly fast.
|
| Even if you didn't give a shit if simple programs were for some
| reason 10000x slower than they needed to be because it meets
| some baseline level of good enough / and or you aren't the one
| impacted by the problems inefficacy creates.
|
| Library authors beneath you would still know and the APIs you
| interact with and the pythonic code you see and the code LLMS
| generate will be affected by that leaky abstraction.
|
| If you think that n^2 naive list appends is a bad example its
| not btw, python string appends are n^2 and that has and does
| affect how people do things, f strings for example are lazy.
|
| Similarly a direct consequence of dictionaries being fast in
| Python is that they are used literally everywhere. The old
| Pycon 2017 talks from Raymond talk about this.
|
| Ultimately what the author of the blog has provided is this
| sort of numerical justification for the implicit tacit sort of
| knowledge performance understanding gives.
| xnx wrote:
| Python programmers don't need to know 85 different obscure
| performance numbers. Better to really understand ~7 general
| system performance numbers.
| oogali wrote:
| It's important to know that these numbers will vary based on what
| you're measuring, your hardware architecture, and how your
| particular Python binary was built.
|
| For example, my M4 Max running Python 3.14.2 from Homebrew
| (built, not poured) takes 19.73MB of RAM to launch the REPL
| (running `python3` at a prompt).
|
| The same Python version launched on the same system with a single
| invocation for `time.sleep()`[1] takes 11.70MB.
|
| My Intel Mac running Python 3.14.2 from Homebrew (poured) takes
| 37.22MB of RAM to launch the REPL and 9.48MB for `time.sleep`.
|
| My number for "how much memory it's using" comes from running `ps
| auxw | grep python`, taking the value of the resident set size
| (RSS column), and dividing by 1,024.
|
| 1: python3 -c 'from time import sleep; sleep(100)'
| dr_kretyn wrote:
| Initially I thought how efficient strings are... but then I
| understood how inefficient arithmetic is. Interesting comparison
| but exact speed and IO depend on a lot of things, and unlikely
| one uses Mac mini in production so these numbers definitely
| aren't representative.
| riazrizvi wrote:
| The titles are oddly worded. For example -
| Collection Access and Iteration How fast can you get data
| out of Python's built-in collections? Here is a dramatic example
| of how much faster the correct data structure is. item in set or
| item in dict is 200x faster than item in list for just 1,000
| items!
|
| It seems to suggest an iteration _for x in mylist_ is 200x slower
| than _for x in myset_. It's the membership test that is much
| slower. Not the iteration. (Also _for x in mydict_ is an
| iteration over keys not values, and so isn't what we think of as
| an iteration on a dict's 'data').
|
| Also the overall title "Python Numbers Every Programmer Should
| Know" starts with 20 numbers that are merely interesting.
|
| That all said, the formatting is nice and engaging.
| _ZeD_ wrote:
| Yeah... No. I've 10+ years of python under my belt and I might
| have had need for this kind of micro optimizations in like 2
| times most
| willseth wrote:
| Sorry, you're not allowed to discourage premature optimization
| or defend Python here.
| Y_Y wrote:
| int is larger than float, but list of floats is larger than list
| of ints
|
| Then again, if you're worried about any of the numbers in this
| article maybe you shouldn't be using Python at all. I joke, but
| please do at least use Numba or Numpy so you aren't paying huge
| overheads for making an object of every little datum.
| jchmbrln wrote:
| What would be the explanation for an int taking 28 bytes but a
| list of 1000 ints taking only 7.87KB?
| wiml wrote:
| That appears to be the size of the list itself, not including
| the objects it contains: 8 bytes per entry for the object
| pointer, and a kilo-to-kibi conversion. All Python values are
| "boxed", which is probably a more important thing for a Python
| programmer to know than most of these numbers.
|
| The list of floats is larger, despite also being simply an
| array of 1000 8-byte pointers. I assume that it's because the
| int array is constructed from a range(), which has a __len__(),
| and therefore the list is allocated to exactly the required
| size; but the float array is constructed from a generator
| expression and is presumably dynamically grown as the generator
| runs and has a bit of free space at the end.
| mikeckennedy wrote:
| It was. I updated the results to include the contained
| elements. I also updated the float list creation to match the
| int list creation.
| lopuhin wrote:
| That's impressive how you figured out the reason for the
| difference in list of floats vs list of ints container size,
| framed as an interview question that would have been quite
| difficult I think
| 867-5309 wrote:
| tfa mentions running benchmark on a multi-core platform, but
| doesn't mention if benchmark results used multithreading.. a
| brief look at the code suggests not
| willseth wrote:
| Every Python programmer should be thinking about far more
| important things than low level performance minutiae. Great
| reference but practically irrelevant except in rare cases where
| optimization is warranted. If your workload grows to the point
| where this stuff actually matters, great! Until then it's a
| distraction.
| amelius wrote:
| Yeah, if you hit limits just look for a module that implements
| the thing in C (or write it). This is how it was always done in
| Python.
| willseth wrote:
| Sometimes it's as simple as finding the hotspot with a
| profiler and making a simple change to an algorithm or data
| structure, just like you would do in any language. The amount
| of handwringing people do about building systems with Python
| is silly.
| ryandrake wrote:
| I am currently (as we type actually LOL) doing this exact
| thing in a hobby GIS project: Python got me a prototype and
| proof of concept, but now that I am scaling the data
| processing to worldwide, it is obviously too slow so I'm
| rewriting it (with LLM assistance) in C. The huge benefit of
| Python is that I have a known working (but slow) "reference
| implementation" to test against. So I know the C version
| works when it produces identical output. If I had a known-
| good Python version of past C, C++, Rust, etc. projects I
| worked on, it would have been most beneficial when it came
| time to test and verify.
| kc0bfv wrote:
| I agree - however, that has mostly been a feeling for me for
| years. Things feel fast enough and fine.
|
| This page is a nice reminder of the fact, with numbers. For a
| while, at least, I will Know, instead of just feel, like I can
| ignore the low level performance minutiae.
| HendrikHensen wrote:
| Having general knowledge about the tools you're working with is
| not a distraction, it's an intellectual enrichment in any case,
| and can be a valuable asset in specific cases.
| willseth wrote:
| Knowing that an empty string is 41 bytes or how many ns it
| takes to do arithmetic operations is not general knowledge.
| oivey wrote:
| How is it not general knowledge? How do you otherwise gauge
| if your program is taking a reasonable amount of time, and,
| if not, how do you figure out how to fix it?
| willseth wrote:
| You gauge with metrics and profiles, if necessary, and
| address as needed. You don't scrutinize every line of
| code over whether it's "reasonable" in advance instead of
| doing things that actually move the needle.
| oivey wrote:
| These are the metrics underneath it all. Profiles tell
| you what parts are slow relative to others and time your
| specific implementation. How long should it take to sum
| together a million integers?
| willseth wrote:
| It literally doesn't matter unless it impacts users. I
| don't know why you would waste time on non problems.
| oivey wrote:
| No one is suggesting "wasting time on non problems."
| You're tilting at windmills.
| cycomanic wrote:
| But these performance numbers are meaningless without
| some sort of standard comparison case. So if you measure
| that e.g. some string operation takes 100ns, how do you
| compare against the numbers given here? Any difference
| could be due to PC, python version or your
| implementation. So you have to do proper benchmarking
| anyway.
| ZiiS wrote:
| This is really weird thing to worry about in python. But is also
| misleading; Python int is arbitrary precision, they can take up
| much more storage and arithmetic time depending in their value.
| Aurornis wrote:
| A meta-note on the title since it looks like it's confusing a lot
| of commenters: The title is a play on Jeff Dean's famous "Latency
| Numbers Every Programmer Should Know" from 2012. It isn't meant
| to be interpreted literally. There's a common theme in CS papers
| and writing to write titles that play upon themes from past
| papers. Another common example is the "_____ considered harmful"
| titles.
| Kwpolska wrote:
| This title only works if the numbers are actually useful. Those
| are not, and there are far too many numbers for this to make
| sense.
| Aurornis wrote:
| The title was meant to be taken literally, as in you're
| supposed to memorize all of these numbers. It was meant as an
| in-joke reference to the original writing to signal that this
| document was going to contain timing values for different
| operations.
|
| I completely understand why it's frustrating or confusing by
| itself, though.
| shanemhansen wrote:
| Going to write a real banger of a paper called "latency numbers
| considered harmful is all you need" and watch my academic cred
| go through the roof.
| AnonymousPlanet wrote:
| " ... with an Application to the Entscheidungsproblem"
| willseth wrote:
| Good callout on the paper reference, but this author gives
| gives every indication that he's dead serious in the first
| paragraph. I don't think commenters are confused.
| dekhn wrote:
| That doc predates 2012 significantly.
|
| From what I've been able to glean, it was basically created in
| the first few years Jeff worked at Google, on indexing and
| serving for the original search engine. For example, the
| comparison of cache, RAM, and disk: determined whether data was
| stored in RAM (the index, used for retrieval) or disk (the
| documents, typically not used in retrieval, but used in
| scoring). Similarly, the comparison of California-Netherlands
| time- I believe Google's first international data cetner was in
| NL and they needed to make decisions about copying over the
| entire index in bulk versus serving backend queries in the US
| with frontends in the NL.
|
| The numbers were always going out of date; for example, the
| arrival of flash drives changed disk latency significantly. I
| remember Jeff came to me one day and said he'd invented a
| compression algorithm for genomic data "so it can be served
| from flash" (he thought it would be wasteful to use precious
| flash space on uncompressed genomic data).
| mwkaufma wrote:
| Why? If those micro benchmarks mattered in your domain, you
| wouldn't be using python.
| coldtea wrote:
| That's an "all or nothing" fallacy. Just because you use Python
| and are OK with some slowdown, doesn't mean you're OK with each
| and every slowdown when you can do better.
|
| To use a trivial example, using a set instead of a list to
| check membership is a very basic replacement, and can
| dramatically improve your running time in Python. Just because
| you use Python doesn't mean anything goes regarding
| performance.
| mwkaufma wrote:
| That's an example of an algorithmic improvement (log n vs n),
| not a micro benchmark, Mr. Fallacy.
| coldtea wrote:
| "Mr. Fallacy."? Got any better juvenile name-calling?
|
| The case is among the example numbers given in TFA:
|
| "Dict lookup by key", "List membership check"
|
| Does it have to spell out the difference is algorithmic in
| this case for the comparison to be useful?
|
| Or, inversely, is the difference between e.g. memory and
| disk access times insignificant, because it's not
| algorithmic?
| PhilipRoman wrote:
| ...and other hilarious jokes you can tell yourself!
| boerseth wrote:
| That's a long list of numbers that seem oddly specific. Apart
| from learning that f-strings are way faster than the
| alternatives, and certain other comparisons, I'm not sure what I
| would use this for day-to-day.
|
| After skimming over all of them, it seems like most "simple"
| operations take on the order of 20ns. I will leave with that rule
| of thumb in mind.
| 0x000xca0xfe wrote:
| That number isn't very useful either, it really depends on the
| hardware. Most virtualized server CPUs where e.g. Django will
| run on in the end are nowhere near the author's M4 Pro.
|
| Last time I benchmarked a VPS it was about the performance of
| an Ivy Bridge generation laptop.
| giantrobot wrote:
| > Last time I benchmarked a VPS it was about the performance
| of an Ivy Bridge generation laptop.
|
| I have a number of Intel N95 systems around the house for
| various things. I've found them to be a pretty accurate
| analog for small instances VPSes. The N95 are Intel E-cores
| which are effectively Sandy Bridge/Ivy Bridge cores.
|
| Stuff can fly on my MacBook but than drag on a small VPS
| instance but validating against an N95 (I already have) is
| helpful. YMMV.
| aunderscored wrote:
| If you're interested, fstrings are faster because they directly
| become bytecode at compile time rather than being a function
| call at runtime
| apelapan wrote:
| Thanks for the that bit of info! I was surprised by the speed
| difference. I have always assumed that most variations of
| basic string formatting would compile to the same bytecode.
|
| I usually prefer classic %-formatting for readability when
| the arguments are longer and f-strings when the arguments are
| shorter. Knowing there is a material performance difference
| at scale, might shift the balance in favour of f-strings for
| some situations.
| mikeckennedy wrote:
| Author here.
|
| Thanks for the feedback everyone. I appreciate your posting it
| @woodenchair and @aurornis for pointing out the intent of the
| article.
|
| The idea of the article is NOT to suggest you should shave 0.5ns
| off by choosing some dramatically different algorithm or that you
| really need to optimize the heck out of everything.
|
| In fact, I think a lot of what the numbers show is that over
| thinking the optimizations often isn't worth it (e.g. caching
| len(coll) into a variable rather than calling it over and over is
| less useful that it might seem conceptually).
|
| Just write clean Python code. So much of it is way faster than
| you might have thought.
|
| My goal was only to create a reference to what various operations
| cost to have a mental model.
| willseth wrote:
| Then you should have written that. Instead you have given more
| fodder for the premature optimization crowd.
| mikeckennedy wrote:
| I didn't tell anyone to optimize anything. I just posted
| numbers. It's not my fault some people are wired that way.
| Anytime I suggested some sort of recommendation it was to NOT
| optimize.
|
| For example, from the post "Maybe we don't have to optimize
| it out of the test condition on a while loop looping 100
| times after all."
| ewuhic wrote:
| This is AI slop.
| Retr0id wrote:
| > Numbers are surprisingly large in Python
|
| Makes me wonder if the cpython devs have ever considered v8-like
| NaN-boxing or pointer stuffing.
| f311a wrote:
| > Strings >The rule of thumb for strings is the core
| string object takes 41 bytes. Each additional character is
| 1 byte.
|
| That's misleading. There are three types of strings in Python (1,
| 2 and 4 bytes per character).
|
| https://rushter.com/blog/python-strings-and-memory/
| thundergolfer wrote:
| A lot of people here are commenting that if you have to care
| about specific latency numbers in Python you should just use
| another language.
|
| I disagree. A lot of important and large codebases were grown and
| maintained in Python (Instagram, Dropbox, OpenAI) and it's damn
| useful to know how to reason your way out of a Python performance
| problem when you inevitably hit one _without_ dropping out into
| another language, which is going to be far more complex.
|
| Python is a very useful tool, and knowing these numbers just
| makes you better at using the tool. The author is a _Python
| Software Foundation Fellow_. They 're great at using the tool.
|
| In the common case, a performance problem in Python is not the
| result of hitting the limit of the language but the result of
| sloppy un-performant code, for example unnecessarily calling a
| function O(10_000) times in a hot loop.
|
| I wrote up a more focused "Python latency numbers you should
| know" as a quiz here https://thundergolfer.com/computers-are-fast
| nutjob2 wrote:
| > A lot of important and large codebases were grown and
| maintained in Python
|
| How does this happen? Is it just inertia that cause people to
| write large systems in a essentially type free, interpreted
| scripting language?
| oofbey wrote:
| It's very natural. Python is fantastic for going from 0 to 1
| because it's easy and forgiving. So lots of projects start
| with it. Especially anything ML focused. And it's much harder
| to change tools once a project is underway.
| passivegains wrote:
| this is absolutely true, but there's an additional nuance:
| yes, python is fantastic, yes, it's easy and forgiving, but
| there are other languages like that too. ...except there
| really aren't. other than ruby and maybe go, every other
| popular language sacrifices ease of use for things that
| simply do not matter for the overwhelming majority of
| programs. much of python's popularity doesn't come from
| being easy and forgiving, it's that everything else isn't.
| for normal programming why would we subject ourselves to
| anything _but_ python unless we had no choice?
|
| while I'm on the soapbox I'll give java a special mention:
| a couple years ago I'd have said java was easy even though
| it's tedious and annoying, but I've become reacquainted
| with it for a high school program (python wouldn't work for
| what they're doing and the school's comp sci class already
| uses java.)
|
| this year we're switching to c++.
| zelphirkalt wrote:
| Omg, switching to C++ for pupils programming beginners
| ... "How to turn off the most students from computer
| programming?" 101. Really can't get much worse than C++
| for beginners.
| xboxnolifes wrote:
| It's very simple. Large systems start as small systems.
| dragonwriter wrote:
| Large systems are often aggregates of small systems, too.
| oivey wrote:
| It's a nice and productive language. Why is that
| incomprehensible?
| hibikir wrote:
| Small startups end up writing code in whatever gets things
| working faster, because having too large a codebase with too
| much load is a champagne problem.
|
| If I told you that we were going to be running a very large
| payments system, with customers from startups to Amazon,
| you'd not write it in ruby and put the data in MongoDB, and
| then using its oplog as a queue... but that's what Stripe
| looked like. They even hired a compiler team to add type
| checking to the language, as that made far more sense than
| porting a giant monorepo to something else.
| tjwebbnorfolk wrote:
| Most large things begin life as small things.
| wiseowise wrote:
| Python has types, now even gradual static typing if you want
| to go further. It's irrelevant whether language is
| interpreted scripting if it solves your problem.
| oofbey wrote:
| I think both points are fair. Python is slow - you should avoid
| it if speed is critical, but sometimes you can't easily avoid
| it.
|
| I think the list itself is super long winded and not very
| informative. A lot of operations take about the same amount of
| time. Does it matter that adding two ints is very slightly
| slower than adding two floats? (If you even believe this is
| true, which I don't.) No. A better summary would say "all of
| these things take about the same amount of time: simple math,
| function calls, etc. these things are much slower: IO." And in
| that form the summary is pretty obvious.
| microtonal wrote:
| _I think the list itself is super long winded and not very
| informative._
|
| I agree. I have to complement the author for the effort put
| in. However it misses the point of the original _Latency
| numbers every programmer should know_ , which is to build an
| intuition for making good ballpark estimations of the latency
| of operations and that e.g. A is two orders of magnitude more
| expensive than B.
| i_am_a_peasant wrote:
| our build system is written in python, and i'd like it not to
| suck but still stay in python, so these numbers very much
| matter.
| zbentley wrote:
| I have some questions and requests for clarification/suspicious
| behavior I noticed after reviewing the results and the benchmark
| code, specifically:
|
| - If slotted attribute reads and regular attribute reads are the
| same latency, I suspect that either the regular class may not
| have enough "bells on" (inheritance/metaprogramming/dunder
| overriding/etc) to defeat simple optimizations that cache away
| attribute access, thus making it equivalent in speed to slotted
| classes. I know that over time slotting will become less of a
| performance boost, but--and this is just my intuition and I may
| well be wrong--I don't get the impression that we're there yet.
|
| - Similarly "read from @property" seems suspiciously fast to me.
| Even with descriptor-protocol awareness in the class lookup
| cache, the overhead of calling a method seems surprisingly
| similar to the overhead of accessing a field. That _might_ be
| explained away by the fact that property descriptors ' "get"
| methods are guaranteed to be the simplest and easiest to optimize
| of all call forms (bound method, guaranteed to never be any
| parameters), and so the overhead of setting up the
| stack/frame/args may be substantially minimized...but that would
| only be true if the property's method body was "return 1" or
| something very fast. The properties tested for these benchmarks,
| though, are looking up other fields on the class, so I'd expect
| them to be a _lot_ slower than field access, not just a little
| slower (https://github.com/mikeckennedy/python-numbers-everyone-
| shou...).
|
| - On the topic of "access fields of objects"
| (properties/dataclasses/slots/MRO/etc.), benchmarks are really
| hard to interpret--not just these benchmarks, all of them I've
| seen. That's because there are fundamentally two operations
| involved: _resolving_ a field to something that produces data for
| it, and then _accessing_ the data. For example, a @property is in
| a class 's method cache, so resolving "instance.propname" is done
| at the speed of the methcache. That might be faster than
| accessing "instance.attribute" (a field, not a @property or other
| descriptor), depending on the inheritance geometry in play,
| slots, __getattr[ibute]__ overrides, and so on. On the other
| hand, _accessing_ the data at "instance.propname" is going to be
| a _lot_ more expensive for most @properties (because they need to
| call a function, use an argument stack, and usually perform other
| attribute lookups /call other functions/manipulate locals, etc);
| accessing data at "instance.attribute" is going to be fast and
| constant-time--one or two pointer-chases away at most.
|
| - Nitty: why's pickling under file I/O? Those benchmarks aren't
| timing pickle functions that perform IO, they're benchmarking the
| ser/de functionality and thus should be grouped with
| json/pydantic/friends above.
|
| - Asyncio's no spring chicken, but I think a lot of the
| benchmarks listed tell a worse story than necessary, because they
| don't distinguish between coroutines, Tasks, and Futures.
| Coroutines are cheap to have and call, but Tasks and Futures have
| a little more overhead when they're used (even fast CFutures) and
| a _lot_ more overhead to construct since they need a lot more
| data resources than just a generator function (which is _kinda_
| what a raw coroutine desugars to, but that 's not as true as most
| people think it is...another story for another time). Now,
| "run_until_complete{}" and "gather()" initially take their
| arguments and coerce them into Tasks/Futures--that detection,
| coercion, and construction takes time and consumes a lot of
| overhead. That's good to know (since many people are paying that
| coercion tax unknowingly), but it muddies the boundary between
| "overhead of waiting for an asyncio operation to complete" and
| "overhead of starting an asyncio operation". Either calling the
| lower-level functions that run_until_complete()/gather() use
| internally, or else separating out benchmarks into ones that pass
| Futures/Tasks/regular coroutines might be appropriate.
|
| - Benchmarking "asyncio.sleep(0)" as a means of determining the
| bare-minimum await time of a Python event loop is a bad idea.
| sleep(0) is very special (more details here:
| https://news.ycombinator.com/item?id=46056895) and not
| representative. To benchmark "time it takes for the event loop to
| spin once and produce a result"/the python equivalent of
| process.nextTick, it'd be better to use low-level loop methods
| like "call_soon" or defer completion to a Task and await that.
| m3047 wrote:
| +1 but I didn't see pack / unpack...
| belabartok39 wrote:
| Hmmmm, there should absolutely be standard deviations for this
| type of work. Also, what is N number of runs? Does it say
| somewhere?
| mikeckennedy wrote:
| It is open source, you could just look. :) But here is a
| summary for you. It's not just one run and take the number:
|
| Benchmark Iteration Process
|
| Core Approach:
|
| - Warmup Phase: 100 iterations to prepare the operation
| (default)
|
| - Timing Runs: 5 repeated runs (default), each executing the
| operation a specified number of times
|
| - Result: Median time per operation across the 5 runs
|
| Iteration Counts by Operation Speed: - Very fast ops
| (arithmetic): 100,000 iterations per run
|
| - Fast ops (dict/list access): 10,000 iterations per run
|
| - Medium ops (list membership): 1,000 iterations per run
|
| - Slower ops (database, file I/O): 1,000-5,000 iterations per
| run
|
| Quality Controls:
|
| - Garbage collection is disabled during timing to prevent
| interference
|
| - Warmup runs prevent cold-start bias
|
| - Median of 5 runs reduces noise from outliers
|
| - Results are captured to prevent compiler optimization
| elimination
|
| Total Executions: For a typical benchmark with 1,000 iterations
| and 5 repeats, each operation runs 5,100 times (100 warmup +
| 5x1,000 timed) before reporting the median result.
| belabartok39 wrote:
| That answers what N is (why not just say in the article). If
| you are only going to report medians, is there an appendix
| with further statistics such as confidence intervals or
| standard deviations. For serious benchmark, it would be
| essential to show the spread or variability, no?
| lcnmrn wrote:
| LLMs can improve Python code performance. I used it myself on a
| few projects.
| lunixbochs wrote:
| I'm confused why they repeatedly call a slots class larger than a
| regular dict class, but don't count the size of the dict
| perrygeo wrote:
| > small int (0-256) cached
|
| It's -5 to 256, and these have very tricky behavior for
| programmers that confuse identity and equality.
| >>> a = -5 >>> b = -5 >>> a is b True >>>
| a = -6 >>> b = -6 >>> a is b False
| esafak wrote:
| The point of the original list was that the numbers were simple
| enough to memorize: https://gist.github.com/jboner/2841832
|
| Nobody is going to remember any of the numbers on this new list.
| mikeckennedy wrote:
| That's a fair point @esafak. I updated the article with
| something akin to the doubling chart of numbers in the original
| article from 2012.
| snakepit wrote:
| This is helpful. Someone should create a similar benchmark for
| the BEAM. This is also a good reminder to continue working on
| snakepit [1] and snakebridge [2]. Plenty remains before they're
| suitable for prime time.
|
| [1] https://hex.pm/packages/snakepit [2]
| https://hex.pm/packages/snakebridge
| sjducb wrote:
| It's missing the time taken to instantiate a class.
|
| I remember refactoring some code to improve readability, then
| observing something that was previously a few microseconds take
| tens of seconds.
|
| The original code created a large list of lists. Each child list
| had 4 fields each field was a different thing, some were ints and
| one was a string.
|
| I created a new class with the names of each field and helper
| methods to process the data. The new code created a list of
| instances of my class. Downstream consumers of the list could
| look at the class to see what data they were getting. Modern
| Python developers would use a data class for this.
|
| The new code was very slow. I'd love it if the author measured
| the time taken to instantiate a class.
| lifeisstillgood wrote:
| I went to the doctor and I said "It hurts when I do this"
|
| The doctor said, "don't do that".
|
| Edit: so yeah a rather snarky reply. Sorry. But it's worth
| asking why we want to use classes and objects everywhere. Alan
| Kay is well known for saying object orientated is about message
| passing (mostly by Erlang people).
|
| A list of lists (where each list is four different types
| repeated) seems a fine data structure, which can be operated on
| by external functions, and serialised pretty easily. Turning it
| into classes and objects might not be a useful refactoring, I
| would certainly want to learn more before giving the go ahead.
| smcin wrote:
| Instantiating classes is in general not a performance issue in
| Python. Your issue here strongly sounds like you're abusing OO
| to pass a list of instances into every method and downstream
| call (not just the usual reference to self, the instance at
| hand). Don't do that, it shouldn't be necessary. It sounds like
| you're trying to get a poor-man's imitation of classmethods,
| without identifying and refactoring _whatever it is that
| methods might need to access from other instances_.
|
| Please post your code snippet on StackOverflow ([python] tag)
| or CodeReview.SE so people can help you fix it.
|
| > _created a new class with the names of each field and helper
| methods to process the data. The new code created a list of
| instances of my class. Downstream consumers of the list could
| look at the class to see what data they were getting._
| calmbonsai wrote:
| You absolutely _do not_ need to know those absolute numbers--only
| the relative costs of various operations.
|
| Additionally, regardless of the code you can profile the system
| to determine where the "hot spots" are and refactor or call-out
| to more performant (Rust, Go, C) run-times for those workflows
| where necessary.
| JBits wrote:
| One of the reasons I'm really excited about JAX is that I hope it
| will allow me to write fast Python code without worrying about
| these details.
| sireat wrote:
| Interesting information but these are not hard numbers.
|
| Surely the 100-char string information of 141 bytes is not
| correct as it would only apply to ASCII 100-char strings.
|
| It would be more useful to know the overhead for unicode strings
| presumably utf-8 encoded. And again I would presume 100-Emoji
| string would take 441 bytes (just a hypothesis) and 100-umlaut
| chars string would take 241bytes.
| robertclaus wrote:
| I liked reading through it from a "is modern Python doing
| anything obviously wrong?" perspective, but strongly disagree
| anyone should "know" these numbers. There's like 5-10 primitives
| in there that everyone should know rough timings for; the rest
| should be derived with big-O algorithm and data structure
| knowledge.
| nodja wrote:
| I think a lot of commenters here are missing the point.
|
| Looking at performance numbers is important regardless if it's
| python, assembly or HDL. If you don't understand why your code is
| slow you can always look at how many cycles things take and learn
| to understand how code works at a deeper level, as you mature as
| a programmer things will become obvious, but going through the
| learning process and having references like these will help you
| to get there sooner, seeing the performance numbers and asking
| why some things take much longer--or sometimes why they take the
| exact same time--is the perfect opportunity to learn.
|
| Early in my python career I had a python script that found
| duplicate files across my disks, the first iteration of the
| script was extremely slow, optimizing the script went through
| several iterations as I learned how to optimize at various
| levels. None of them required me to use C. I just used caching,
| learned to enumerate all files on disk fast, and used sets
| instead of lists. The end result was that doing subsequent runs
| made my script run in 10 seconds instead of 15 minutes. Maybe
| implementing in C would make it run in 1 second, but if I had
| just assumed my script was slow because of python then I would've
| spent hours doing it in C only to go from 15 minutes to 14
| minutes and 51 seconds.
|
| There's an argument to be made that it would be useful to see C
| numbers next to the python ones, but for the same reason people
| don't just tell you to just use an FPGA instead of using C, it's
| also rude to say python is the wrong tool when often it isn't.
| CmdrKrool wrote:
| I'm confused by this: String operations in Python
| are fast as well. f-strings are the fastest formatting style,
| while even the slowest style is still measured in just nano-
| seconds. Concatenation (+) 39.1 ns (25.6M ops/sec)
| f-string 64.9 ns (15.4M ops/sec)
|
| It says f-strings are fastest but the numbers show concatenation
| taking less time? I thought it might be a typo but the bars on
| the graph reflect this too?
| pvtmert wrote:
| There are lots of discussions about relatedness of these numbers
| for a regular software engineer.
|
| Firstly, I want to start with the fact that the base system is a
| macOS/M4Pro, hence;
|
| - Memory related access is _possibly_ much faster than a x86
| server. - Disk access is _possibly_ much slower than a x86
| server.
|
| *) I took x86 server as the basis as most of the applications run
| on x86 Linux boxes nowadays, although a good amount of
| fingerprint is also on other ARM CPUs.
|
| Although it probably does not change the memory footprint much,
| the libraries loaded and their architecture (ie. being Rosetta or
| not) will change the overall footprint of the process.
|
| As it was mentioned on one of the sibling comments -> Always
| inspect/trace your own workflow/performance before making
| assumptions. It all depends on specific use-cases for higher-
| level performance optimizations.
___________________________________________________________________
(page generated 2026-01-01 23:00 UTC)