Live data from Hacker News

Extending Python with Rust

maxwellrules.com

61–70 of 88 posts

Re: Extending Python with Rust

#61
If you're writing a compiled extension, you can use Rust. Or Cython. Or C. Or C++. Which should you use?

TL;DR:

* If you're wrapping existing C library, I'd use Cython.

* If you're wrapping existing C++ library, I'd use PyBind11 (no personal experience, but it's based on Boost::Python, which I have happily used). Cython in theory does C++ but it's a frustrating, limited experience.

* If you're writing a tiny library and you don't know Rust, and you're not worried about memory safety, Cython is nice.

* For anything involving writing extensive new low-level code, Rust with PyO3. Memory safety _will_ bite you in the ass. Concurrency is vastly easier with Rust. You get a package manager for dependencies. You're not writing a pile of code in a language without good tooling (Cython).

Long form, with more alternatives and use cases: https://pythonspeed.com/articles/rust-cython-python-extensio...

Re: Extending Python with Rust

#62
post #6

Earlier quoted context omitted.

And note that for example on Apple M1 it's essentially impossible to beat an implementation that uses Apple's Accelerate library for things like matrix multiplication, because Apple uses undocumented instructions unavailable to the public in that library.

Deprecated since version 1.20: The native libraries on macOS, provided by Accelerate, are not fit for use in NumPy since they have bugs that cause wrong output under easily reproducible conditions. If the vendor fixes those bugs, the library could be reinstated, but until then users compiling for themselves should use another linear algebra library or use the built-in (but slower) default, see the next section. Sourc…

Added back the very next release

https://numpy.org/doc/stable/release/1.21.0-notes.html

> With the release of macOS 11.3, several different issues that numpy was encountering when using Accelerate Framework’s implementation of BLAS and LAPACK should be resolved.

Re: Extending Python with Rust

#63
post #36

Earlier quoted context omitted.

NumPy is insanely fast for most use cases, I've (grudgingly) come to the conclusion that if NumPy doesn't hack it then I should re-think the problem or my way of solving it rather than to try to optimize that particular bit of code if it isn't meant for something that is going to be run in production on a large number of machines. Likely there are better uses of my time. It's interesting how what is nominally a scrip…

How do you handle it in numpy if you want something like ((x - y)/2 + z) * w all elementwise over the arrays? Naively that's 3 intermediate unused arrays being created.

Is there an optimal solution to that for all sizes of the arrays?

E.g. I’d expect for small/medium then re-writing as a fold over the input arrays would be fastest because you’d only traverse once & everything would fit in cache.

However if the 3 arrays combined size is larger than L1 cache, I’d be strongly tempted to bet on the naïve 3 operation approach to be faster. You’d save so much time on cache line flush / reload activities.

But then if 2 arrays are larger than L3 i’d expect going back to the fold / single traversal to win again because the iterating 2 arrays at a time behaviour is no longer any different to iterating 3 at a time.

Untested hypothesis.

Re: Extending Python with Rust

#64

Earlier quoted context omitted.

I tried once building a very simple Rust extension on Windows using the Anaconda Python distribution and had no luck. If I remember correctly it got stuck trying to find the VC++ compiler. Cython extensions on the other hand work fine with the MinGW GCC compiler provided by Anaconda, although it's pretty old (version 5).

In my experience that is just the silly amount of variables you have to export to use the VC++ compiler and this is why they provide a dedicated terminal prompt for the VC++ compiler with all the right env vars loaded. I've struggled as well automating VC++ compilation in rust as a result. If you're so inclined to revisit this using mingw with rust you can see how here https://rust-lang.github.io/rustup/installation/…

I forgot to mention that I didn't have VC++ installed at all (because it's not free from all points of view).

If I remember correctly I tried installing the MinGW based Rust toolchain too, but I'll try again with the instructions you've given me.

Re: Extending Python with Rust

#65
post #40

To be honest, that Rust syntax looks pretty horrible for something as simple as multiplying a vector by a scalar.

Definitely. However, the equivalent for the array multiplication in numpy looks like this:

https://github.com/numpy/numpy/blob/22e683d84f2584a6f9a57b2c... + https://github.com/numpy/numpy/blob/22e683d84f2584a6f9a57b2c...

To know what they do, you need the source for INPLACE_GIVE_UP_IF_NEEDED (https://github.com/numpy/numpy/blob/b222eb66c79b8eccba39f46f...) and PyArray_GenericInplaceBinaryFunction (I don't even know where that's coming from, it's not defined in Numpy, maybe it's part of the Python interface?).

In the end, both are unreadable in their own way. I personally prefer the Rust version above to the macrofied C version that's in Numpy but that's a matter of taste. I'd also trust the safe Rust implementations more than the C implementations because of the memory management guarantees Rust provides, though I suppose for simple operations like multiplication it'll be easier to make the program safe enough in C.

Re: Extending Python with Rust

#66
post #12

I've been through the journey with compiling extensions to Python in lower languages (primarily C and C++) and it's really worth evaluating the benefit. In general, the control that it gives you over memory layout and chaining of operations make it very difficult to say from toy examples what benefit you'll get. If you test a single array operation with NumPy then it will compare favourably to a low level implementat…

I've written a couple of C-extensions for a Python service that needed to run in real-time. We started out 100x slower than real-time and managed to make it slightly faster than real-time in a hectic couple of weeks. The main bottleneck that C-extensions solved for us then was marshaling. Instead of doing something like Numpy --> custom algorithm --> Numpy --> custom algorithm in Python, we moved it all to C. This wa…

It's not really meaningful to say "100x slower than real-time" or "slightly faster than real-time", "real-time" isn't some threshold of sub-second precision. It usually refers to relative (though maybe absolute wall-clock) time of execution for instructions that are typically controlling hardware that needs precise timings (ie. the time between turning this thing on and turning it back off needs to be exactly 150us).

Re: Extending Python with Rust

#68
post #46

Earlier quoted context omitted.

Yeah, without knowing much about Rust, I’ve decided from these types of posts that I should gravitate toward “app I use but now it’s in Rust.” I use plenty of Rust programs simply because people seem excited about it.

Yeah that's one genre of these posts - someone sharing their experience (re)writing *nix utils like cd, ls, grep etc in Rust. But as a project I think it's unrealistic to expect they'll supplant those utilities with an identical Rust-based one, there's often not much to be gained for the risk of potentially breaking a bunch of important stuff in your system by introducing a bit of an unknown quantity, and the effort…

> As I said, not a Rust dev and frankly I'm quite intimidated by all the rants people had about fighting with "The Borrow Checker" which sounds like a ferocious Elden Ring boss. But I am tempted by the way it allows safe (or safer) software to be written.

In my experience, those "fighting" the borrow checker are often novices misunderstanding the language model or lacking the experience necessary to effectively use it, or extremely advanced programmers running into compiler limitations and bugs. In most Rust code there's very little fighting the borrow checker.

Compare it to people saying they're fighting the compiler or fighting the interpreter when they try to multiply a string by an array and only errors come out, or trying to reassign a const value and cursing at the compiler for getting in their way. The error messages are often unhelpful and unclear (though I have to give Clang and Rust that their error messages are quite good in these days) but the core "fight" is trying to do something that doesn't make sense within the context of the programming language.

For Rust, there are additional limitations on top of what your average JS/C#/Java/Python programmer will be used to. These limitations are often also present in C (you can't just share memory between threads willy-nilly, or pass around freed pointers!) where they're classified as undefined behaviour, hopefully with a warning in the console, but in Rust you must deal with the error that's been detected.

The borrow checker is an extra constraint that's present in many modern C++ code bases as well, though the compiler lacks proper analysis support in many areas. It takes some getting used to memory ownership and the limitations and possibilities associated with it, but in many cases listening to and understanding the borrow checker will give you much better code (more correct code but also often clearer code) than ignoring the warnings like you would in C++.

For most programming languages, a beginner needs to learn 1) installing/calling the tooling 2) the core language syntax 3) the special magic features of the language and 4) how to distribute the compiled code. With Rust there's an intermediate step between 2 and 3, the borrow checker, which beginners tend to underestimate or dismiss (I certainly did) despite the warnings in any guide. It's not especially complicated, but it's an extra step other language might lack.

I recommend giving learning Rust a go. Don't be like me, don't skip the uninteresting parts of the Rust Book (https://rust-book.cs.brown.edu/, chapter 4 is what I'm referring to), you'll only find yourself getting more frustrated. Chapter 17 (fearless conspiracy) is where I fell in love with the language, but to get through it I had to go back and actually read about ownership.

Re: Extending Python with Rust

#70
post #10

This is great but you should be aware of the cost. Distributing binary wheels for every available platform (including ARM, MacOS, ...) and implementation (PyPy...) is not easy, and not doing it causes really abysmal user experience (doing `pip install requests` and being told you need to install a Rust toolchain to build `cryptography`). Sometimes the performance might not be worth it. Thankfully there are GitHub Act…

Yeah, Python's dependency management is already abysmal, but when lower level language modules get involved its suddenly whole different level of hell. This actually put me off Python entirely. Sadly no real alternative in ML.

abysmal compared to?
Post reply on HN