Live data from Hacker News

Ask HN: C/C++ developer wanting to learn efficient Python

news.ycombinator.com

41–50 of 50 posts

Re: Ask HN: C/C++ developer wanting to learn efficient Python

#41
post #35

Python is a very fast language, but not in the sense that you would expect as a C++ developer: its execution is (comparatively) very slow, but it shines at the speed of development. Many things that one might take as given in other languages, in Python are optional : static type analysis, multithreading, immutability and the like. When it comes to writing algorithms in Python, it's best to think about it as executabl…

Yeah, I need to take some forget-me-nots when I have to work on the service our system's architect said had to be in Golang, and is constantly undergoing API changes because things keep getting added/changed. It currently serves ~1000 requests per day, with a goal of ~60,000 if we increase uptake across teams. Much traffic. Wow. My last job I built and maintained a group of Python microservices that handled ~500 requ…

> And if I hear some flavor of "compile-time checks are tests/but Python doesn't check types" argument, that person shouldn't be involved in software development.

Have you ever even used a powerfull type system? I dont think so

Re: Ask HN: C/C++ developer wanting to learn efficient Python

#42

Advanced Python Mastery: https://news.ycombinator.com/item?id=36785005 Book: High Performance Python

I'm the co-author of High Performance Python, Micha and I are working on the 3rd ed (for 2025). Lots of bits of the book came from my past conference talks, they're available here (and the public talks will generally be on youtube): https://speakerdeck.com/ianozsvald Mostly that content has a scientific focus but the obvious thing that carries over to any part of Python is _profiling_ to figure out what's slow. Top t…

Ooh that ipython extension is nice. Thanks!

Re: Ask HN: C/C++ developer wanting to learn efficient Python

#43
Going through the official Python tutorial is a must:

https://docs.python.org/3/tutorial/

Use multiprocessing not multithreading due to the GIL.

Python optimization is generally at a higher level, you use a better algorithm to get a result faster, rather than you saved an unnecessary copy.

Know when to use Python async.

Python is slow but you can always move work over into the database or dedicated library like NumPy.

Use the faster Python interpreter PyPy, which does JIT.

Do a multiprocessing pipeline with your CPU bound work broken down into stages.

Re: Ask HN: C/C++ developer wanting to learn efficient Python

#44

Earlier quoted context omitted.

"- Python's dict is a fast unordered hashmap. However, if you need order-aware operations like C++'s std::map::lower_bound(), you're out of luck; Python's standard library doesn't have a tree implementation." I'm fairly certain that in Python 3.7 and later standard library dictionaries are now ordered by default.

Ordered by insertion (see [Mailinglist]( https://mail.python.org/pipermail/python-dev/2017-December/1... )) This might or might not be what you want/expect...

You are correct. Dicts are ordered by insertion. Also, I'd like to add that, maybe surprisingly, sets are not.

    >>> set([3, 2, 1])
    {1, 2, 3}

    >>> set([10, 100, 1000])
    {1000, 10, 100}

Re: Ask HN: C/C++ developer wanting to learn efficient Python

#45
post #23

In Python job interviews, I think the interviewer will only judge your code on asymptotic complexity, not absolute speed. I think Python engineers generally aren't expected to know how to micro-optimize their Python code. Some general tips for algorithmic complexity in Python: - Python's list is equivalent to C++ std::vector. If you need to push/pop at the head of the list, use Python's "collections.deque" to avoid t…

"- Python's dict is a fast unordered hashmap. However, if you need order-aware operations like C++'s std::map::lower_bound(), you're out of luck; Python's standard library doesn't have a tree implementation." I'm fairly certain that in Python 3.7 and later standard library dictionaries are now ordered by default.

Yes, Python dicts remember insertion order. This is different from C++ std::map, which maintains the keys in sorted order. For example, std::map::lower_bound(X) finds "the smallest key in the map which is greater than or equal to X" in O(log(N)) time. I don't think Python has any data structure in the standard library that supports this operation while also supporting insertion in O(log(N)) time.

Re: Ask HN: C/C++ developer wanting to learn efficient Python

#46
post #42

Earlier quoted context omitted.

I'm the co-author of High Performance Python, Micha and I are working on the 3rd ed (for 2025). Lots of bits of the book came from my past conference talks, they're available here (and the public talks will generally be on youtube): https://speakerdeck.com/ianozsvald Mostly that content has a scientific focus but the obvious thing that carries over to any part of Python is _profiling_ to figure out what's slow. Top t…

Ooh that ipython extension is nice. Thanks!

Thanks :-) I use it for all my talks and finally decided I'd better start sharing it a bit. It really is useful to understand the memory cost of things like Pandas operations

Re: Ask HN: C/C++ developer wanting to learn efficient Python

#48
post #32

I have been writing python for 15 years now and only occasionally have needed or wanted to optimize algorithms. When I have, the general takeaways have been: * Flattening data from hierarchical, custom types into flatter, builtin collection types can make a speed difference. The builtin type methods spend more time in the native code and are optimized. * Lots of things that I thought could make a difference but would…

> There is so much pointer indirection internal to CPython.

This cannot be overstated. Really, truly, forget everything you know about cache locality when doing even basic "for-each: add numbers together"-type things in Python.

That's not an indictment; such indirection is par for the course for scripting languages, and enables a lot of features and dynamism. Additionally, the cost of that indirection may go down over time thanks to interpreter optimizations like the JIT: https://tonybaloney.github.io/posts/python-gets-a-jit.html

Re: Ask HN: C/C++ developer wanting to learn efficient Python

#49
Much of the other advice here is spot on, and is definitely the first place you should look.

That being said, if you really are constrained by pure-python speed for ordinary tasks (and your first resorts of native code/multiple processes/parallelize IO aren't available), there is a large array of (often horrifying) dirty tricks you can use to eke out a few tens of percent of speed improvements.

Here are some random examples that come to mind, roughly sorted in order from "somewhat advanced but useful things to know or do" to "disgusting; why are you even using Python?":

- Be familiar with BytesIO, memoryview, and the buffer protocol. Using these can dramatically improve memory efficiency (and even bring back a little bit of cache locality benefits in Python's internal pointer hell) and reduce copies. If you're coming from C++, abandon all hope of ever getting to zero copies, but careful use of BytesIO can bring the number way down, and unlike other hacks on this list it doesn't damage the intelligibility of your code that much.

- Be deeply suspicious of others' broad statements about the GIL. These are often wrong in both directions: many things that you'd assume are not GIL-bottlenecked (independent calls into some native libraries) end up running in sequence due to the GIL; on the other hand, many things can be truly parallelized using native Python threads--even some non-I/O tasks (some numpy operations, some cryptography/compression libraries). Benchmark early and often.

- Use tuples wherever possible instead of lists (but if you find yourself casting back and forth, just use lists). This only occasionally brings performance benefits (e.g. via small-tuple reuse), but it's a good practice anyway: don't add unnecessary mutability.

- Not all functions are created equal. Functions with small number of positionals and no kwargs are marginally faster to call than functions with kwargs/variadics/more complicated signatures.

- When using functional list processing functions (e.g. sort, map), the functions in the operator module are much faster than lambdas; use them if you can.

- Keep the cost of function calls in mind when writing or using decorator-heavy code. Each decorator is usually an added function call, and often the more expensive (varargs/complex signature) kind to boot.

- functools.partial can be slightly faster than wrapper functions if your arguments are uniform.

- Relatedly, if you are using decorators for non-intercepting purposes (like registering functions/classes by decorating them), make sure your decorators are returning the passed-in function directly rather than a wrapper. That reduces their runtime cost to zero.

- This isn't really algorithmic but: if you're suffering from the startup time or CPU hit from lots of invocations of small/fast standalone scripts, turn off bytecode caching. While the act of compiling bytecode is nearly free speed-wise, the I/O hit of writing the bytecode back to the filesystem can be surprisingly high. Bytecode caching was such a mistake.

- When using multiprocessing, share data via fork(2) wherever possible. This makes it zero-cost to access largely read-only data in your parallel processes. I talked at length about this here and in adjacent comments: https://news.ycombinator.com/item?id=36941892

- Don't be afraid to drop back to bytes for hot-loop string manipulation (unless, of course, you need non-ASCII characters). Some operations can be very slightly faster on bytes, but don't assume strings are always slow. Also, just like tuples/lists, lots of code just implicitly converts supplied bytes to strings internally anyway, so if you're passing them to a library make sure you know what it's doing.

- Cache dot lookups for things (even stdlib module accesses/methods) in variables next to your hot loops. This makes code pretty ugly and is at the top of my list for things that I hope interpreter optimizations/JIT can more reliably help with over the long term. There's already a bit of optimization done in this area so it may not turn out to help as much as you think it will.

- You can live-patch classes to amortize the overhead of __getattr[ibute]__ and property descriptors by binding new methods/fields at runtime and saving a bunch of dictionary hits. This isn't a panacea since it does require you to trade away slotted speedups in some cases, and MRO cache invalidation can cause it to hurt more than it helps. As always, benchmark.

- Relatedly, the presence of __getattr/__setattr anywhere in the MRO for a class is a bit of an optimization fence for speeding up method calls. The situations where this hurts performance have changed a lot between interpreter versions, but if you're using OO code in hot loops, removing those dunder methods from your class hierarchy is a good next step to try after caching away self-dot lookups.

- Don't access global variables in your hot loop; function-local variable lookups are a tiny bit faster (though this is an area where optimizations may moot this advice in the future). Remember that instance variables ("self.foo") are slower than both because of the dictionary lookup in the dot.

- If using multiple Python threads (even if most of them are backgrounded/waiting on IO, e.g. Sentry or database drivers), you can override the interpreter switch/check intervals in your hot loops. I've seen this work more than once, but very rarely.

- If for some strange reason you have lots of small fast IOs in your hot loop, you can locally change interpreter buffering behavior (or drop to lower-level os.[read|write] calls and manage your own buffering) for a marginal speedup.

- In some very very rare cases, typing.Generic can actually add runtime overhead; benchmark with and without it.

- An easy win for small-script startup times is to remove locations from the module search path. If you strace(2) your program's compile pass (replace main with a sleep and strace until that), you'll often see it statting handfuls of (missing) locations per import before it finds the module. This only saves a little bit of time since filesystems tend to be good at metadata caching.

- Seriously, function calls are expensive. If you can't inline them, the awful generator hack can save you a few % of function call overhead: turn your function call body into the inner loop of an infinite generator, create the generator outside of your hot loop and cache gen.send/gen.next in variables to "call" the function by sending values into the generator (fun fact: gen.next is faster than next(gen)). But seriously, if you find yourself in a situation where this makes a difference, go for a smoke and rethink your life choices.

Re: Ask HN: C/C++ developer wanting to learn efficient Python

#50
I like to joke that to make python fast you need to avoid using python. A ton of python stuff is C/C++ or Fortran wrapped in python, numpy, pandas, pytorch etc is what I have experience with but this applies in all domains. A huge part of learning to write fast python is to first learn these libraries, and second learn about how data is shepherded back and forth. Your experience with C/C++ gives you a good background to understand this!

That being said it's totally possible to write pure python that is much faster than other ways of doing pure python, learn how its all implemented if you are interested, and profile like crazy! Good luck!

Post reply on HN