Ask HN: Resources to learn high-performance C++/Rust?

10 points by o10449366 ↗ HN
I'm a programmer that's mostly worked with Python so far in my career. At various points, I've run into performance bottlenecks that others have solved by writing high-performance libraries in C++ and Rust, mainly for things like data iterators and network servers.

What are some good resources to learn how to write high-performance and low-latency code that can interface with popular languages like Python in 2022? There are a lot of open-source libraries I can analyze and study, but often I feel like the techniques and optimizations they use go way over my head.

7 comments

[ 3.0 ms ] story [ 27.4 ms ] thread
This may be a bit of a cop out, but compiled, runtimeless languages are so astonishingly faster than Python on average (especially for things like numeric computation in loops) that you don't actually need to go out of your way to write optimized code. If this is your first systems-level language, the I'd say just focus on getting your head around things like pointers and the stack vs. heap, and you'll find that your code will be plenty fast. Only then,if you really really need more optimization, will it be worth your time to learn about advanced optimization techniques.
The one caveat to this is that it's relatively easy to do large amounts of dynamic memory allocation/copying in C++ which can run worse than a scripted language. Especially if you std::shared_ptr all the things, or do lots of things with std::string.

I wrote a parsing algorithm in Java and C++, and the Java one was absurdly faster than the C++ one at one point because I was using std::vectors all over the place, so any time I needed to store an attribute in one of those it was doing memory allocation that was way slower than Java's memory allocation and I wasn't getting a lot of the benefit of C++ because of the memory indirections being similar to the Java version. Similarly doing a lot of += with std::strings will utterly destroy your performance due to all the allocations, deallocations, and copying.

Expanding on strings a bit more: There's a really interesting attribute of a language like C++ versus one that has a GC and immutable strings. Because the memory lifetime is managed for you in a GCed language, there is effectively no need to actually copy string memory when handing it off to other libraries. Counter to that, because libraries have no idea what the lifetime of a string will be in C++, they effectively always have to copy strings in order to not have huge memory bugs. This actually leads to more performant code in the GCed language in this specific case due to there being less copying, without having some way to either modify the source of the other library or tell it in some way that the lifetime of the memory will always be good when it wants to access it. I think Rust lifetimes allow for similar optimization because it shouldn't compile if the memory gets deallocated while something else has a reference to it. Just an interesting observation I noticed a little while ago when working on heavy string processing code.

I'd say it's quite hard to make C++ approach the speed of Python even with heavy allocation and frequent copying. My ballpark is that Python code will be between 10 to 100 times slower than the equivalent C++ or Rust code. Java's an entirely different beast, and can definitely be on par with allocation-heavy C++, though you're not going to be writing Java functions that can be called from within Python. And your intuition about Rust is correct, there's no pressure to defensively copy strings in Rust as there is in C++, you can just safely pass and store the references.
Your C++ code can even be much slower than Python if you are not careful. Here's a good example of a StackOverflow question asking why it takes a longer time to split a string in C++ than in Python:

https://stackoverflow.com/questions/9378500/why-is-splitting...

Python's objects are ref-counted and stored on the heap, so copies do not happen unless you make it explicit. And Python's strings are all interned and stored in a single hashtable, so duplicate strings will not consume any more memory. These properties makes Python quite competitive with naively written C++/Rust. You can certainly write fast string in C++/Rust using string slices (std::string_view in C++ and &str in Rust), but in C++ you need to worry about any memory-related errors and in Rust you need to fight with the borrow checker. I also think that if you really want the best performance in C++/Rust then you need to write your own string class tailor-made to your problem.

Rust can introduce some interesting performance issues when it comes to implicit Drop when leaving an object-in-memory's scope.
That's just RAII, and even if you were repeatedly allocating and dropping something large within a tight loop I think you'd have a hard time noticing compared to the overhead of Python interop. You could probably find a way to avoid those allocations, but that's intermediate-level optimization that can also be deferred to future work.
Definitely, especially compared to Python. I bring it up because it can be counterintuitive when writing a naive solution from a managed language in something like Rust for performance reasons.