Some reasons to avoid Cython
pythonspeed.com
pythonspeed.com
After writing a couple of python decoders [2] for movie encodings from the 90's it got old quickly.
As luck would have it, FFmpeg has support for almost all video encodings under the sun. For my usecase I wanted to send one frame per time to FFmpeg to decode.
Luckily I found PyAV[3]. It's a Cython project which binds to FFmpeg.
Which brings me to the article. It reads more like a C bad, rust good. Cythons tag line is: 'Cython gives you the combined power of Python and C`
Just wanting speed and less memory bugs, then rust will fare better. If you want to have the combined power of Python and C then Cython is pretty cool.
[1] https://github.com/rvanlaar/QTVR [2] https://github.com/rvanlaar/QTVR/tree/master/qtvr/decoders [3] https://github.com/PyAV-Org/PyAV
Agreed. Sometimes it is fun (and useful) to use tools that can blow your feet off if misused.
"two compiler passes being a problem" again this is if you are writing big tracts of C code in your cython; not how it's normally used.
"No standardized package or build system for dependencies" / "all the incentives push you to write everything from scratch in Cython, rather than reuse preexisting libraries." - I dont really understand this part, is this just a general C/C++ does not encourage the use of other native dependencies? We are using Cython to write Python code that is more optimized than plain Python. our dependencies are normally going to be other Python dependencies. If our Cython is to wrap some well known native library, then that has to be installed also when the Python wheel is installed, and that doesnt change if your Python wheel was built from Rust source or C/C++ source.
We use Cython in SQLAlchemy to tremendous effect and excellent integration with existing Python code, including being able to fall back to pure Python (so that our source install runs even if you dont have a compiler or are using Pypy), and we've had zero user issues /bugs / anything. We will consider the Rust tools once they've had several years of maturity and widespread use under their belts (meaning, they'd have to meet or surpass cython's popularity), otherwise we aren't going to hoist that on our userbase anytime soon.
I think Cython is great for just speeding up profiling-revealed hot spots. And `cython --annotate` is even a nice helper along that path. { In fact, I think gcc should have a similar system one could integrate so that you can click-expand the Python to get the C and then click again the C to get the assembly. :-) } It really makes Python more like the gradually typed system Common Lisp always was.
In fact, there was talk back in the very early noughties of bundling a precursor of Cython with the Python interpreter itself. I was always a bit disappointed that didn't go very far. Ah well.
It's glorious in its simplicity.
Nothing wrong with using ctypes. It's the right solution for some things. However, cython is generally easier with numpy than numpy.ctypeslib
It's definitely a good tool to have in your toolbox!
"Wonky" and "terrifying", a lot more. ctypes is... useful, but it also uses somewhat strange terminology which can be hard to match to C's as it's trying to bridge C and Python. And when getting it wrong is an UB, it's pretty frustrating.
Sorry, not interested. I can’t think in Rust. Tried many times. Things like dynamically updated graphs are nearly impossible to write in Rust, and concurrency is less than pleasant. Fighting the borrow checker is not my idea of a good time.
I don’t understand why everyone is so fascinated with Rust. I am like 3 times less productive there, and there is absolutely no pleasure for me in writing Rust code.
I’ll stick to Python and C++, thank you.
Can you expand on this? I’ve taken only a cursory look on Rust and it’s not obvious to me what are the specific constraints that would cause this.
https://doc.rust-lang.org/book/ch15-04-rc.html
for things that are too dynamic for borrow checking.
Reference counting works great for the things it is good for but it does get into trouble with cycles and many of us would say that Java's memory allocator/garbage collector is worth what it costs.
My opinion is that automated memory management is a key concept for software reuse and if you look at the problems of the C/C++ world this is pivotal. That is, the range of memory management relationships you might want between a library and its client is pretty wide, I mean sometimes you want a library to make its own buffers, other times you want to hand it an existing buffer, if it is building graphy structures it needs to allocate stuff, do you really want it to use malloc? do you want to pass it your own malloc? etc.
The Java answer of providing a standard answer to allocation and garbage collection makes libraries composable with code in a way that Rust struggles with. (In the end rust libraries have to fall back to RC when complexity gets too high)
(and basically implementing your own cycle-aware garbage collector, which again is not my idea of a good time).
Among other things, allocations and finalizations are considerably faster.
When a non-trivial payload is construed mostly of large linked structures where each element is malloced, using platform memory management may results in performance profile where over quarter of the run time is spent on malloc and free. Am not kidding.
Of course you don't malloc anything. You do have "newNode" method or such that increments size. When size reaches capacity, you allocate a new larger array and copy data to it. When nodes "finalize", you store the index, and gc at the resize time (if you want to - there are lots of algorithms where you actually always only want to add data to the datastructure).
Deep copy everything, you say? Hell yes, it's way way faster to have few large memory allocate/copy operations than a datastructures worth of node pointers.
And if you don't care about performance, you probably shouldn't be using a systems programming language in the first place :D
The above of course only makes sense if you have really large business critical datastructures - it's not an endorsement to rewrite every dictionary, set or a linked list. But the message to which I replied to was about "when writing your own datastructure".
I'm a Rust connoisseur, but I'd agree with 'nearly impossible to write', which is why I'd (first of all) try to grab a library, assuming I'm doing anything complicated with graphs. If it's very simple and specific, I'd try to go through the list of possible graph representations (eg. adjacency lists), and pick a suitable one, but never store nodes directly, rather store indices (while the nodes are stored in some sort of vector).
Requires a bit of boilerplate in the beginning, but pays off when actually needing to work with your data.
which gives a big boost to plain ordinary Python code, particularly branchy and dynamic stuff like
https://rdflib.readthedocs.io/en/stable/
where it made the difference between a system I was working on being tolerable and not tolerable.
https://github.com/PyO3/maturin
It basically automates everything for you. If you use it with Github actions, it will compile wheels for you on each release for every platform and python version you want, and even upload them to PyPi (pip) for you. Everything feels very modern and well thought out. People really care about good tooling in the Rust world.
(yes, this particular bit of rust evangelism was not obvious from the headline)
What's the best way to learn enough Rust to do this? My code is basically just some Numpy array manipulation, with some unfortunate for-loops which can't be vectorized, which is the source of the slow speeds.
I've found Chat GPT to be really excellent for quickly getting myself up to speed with languages that I'm not familiar with.
I will search myself, but is there are a great rust syntax reference doc?
If you actually want to learn rust then that’s a different story and you should probably check out Steve Klabnik‘s book or something like that (or just look at the sample code I linked to in my other comment from my own recent rust library for python).
https://github.com/Dicklesworthstone/fast_vector_similarity/...
Basically you use ndarray instead of numpy, try to vectorize anything you can, and for the for loops that can’t be vectorized, you can use rayon to do them in parallel.
I got the idea from CMake, which also has absolutely nothing to do with Python but is best installed via Pip. It's a package manager that basically works and is basically always available on Linux and Mac (among programmers anyway).
One of the few areas of Python that doesn't completely suck.
from libcpp.vector cimport vector
from libcpp.pair cimport pair
cdef class PointVec:
cdef vector[pair[float, float]] vec
def __init__(self, points: list[tuple[float, float]]):
self.vec = points
def __repr__(self):
result = ", ".join(f"({x}, {y})" for x, y in self.vec)
return f"PointVec({result})"
def __setitem__(
self, index, point: tuple[float, float]
):
cdef pair[float, float] *p = &self.vec.at(index)
p.first = point[0]
p.second = point[1]
def __getitem__(self, index):
return self.vec.at(index)Opt-in safety is clearly worse than opt-out safety.
Rust evangelists are big on safety guarantees and while that's a nice feature I'm not convinced it's The Most Important Thing Ever.
https://docs.julialang.org/en/v1/stdlib/InteractiveUtils/#In...
Polars seems to have a good model where it uses the Arrow in memory format, which has implementations in Python and Rust, and makes a lot of the ndarray stuff easier. However, if the Rust libraries are not written with Arrow first, they become quite hard to work with. For example, there are many libraries written with https://github.com/rust-ndarray/ndarray, which is challenging to interop with Numpy.
(I am not an expert at all, please correct me if my characterizations are wrong!)
tl;dr "Don't use Python/Cython or C/C++. Use Rust instead, it's better." is basically that article.
from libcpp.vector cimport vector
for this blog post to make sense.