11 ms·
Test for lists in Cython
- Nimitz14 5y agoone word: pybind11 I honestly think people are severely underestimating what a massive impact this is currently having in increasing productivity of python devs who know a little C++.
- otabdeveloper4 5y agoI concur, pybind11 is the smoothest Python extension story I've experienced so far.
- sapo 5y agoWhen considering python standard types and ease of use, such as lists and dicts
- ur-whale 5y agoFrom the benchmarks: > Rust is not that fast because it needs to copy data; I'm surprised. Don't know much about Rust, but isn't it hailed as being competitive with C/C++ ?
- iExploder 5y agorust has concept of references, maybe it was referring to its particular implementation of the problem
- efaref 5y agoThe implementations for Rust and Julia are different in a way that means the comparison is not really apples-to-apples. The Julia implementation replaces the input list with a Julia object, which is of course trivial for Julia to iterate over. The Rust implementation is asked to convert the list-of-list-of-floats back to Python types, and then back into Rust types. Of course converting 100M floats back and forth is going to take a non-trivial amount of time. The equivalent in Rust would be to wrap the return value in a PyObject that contains the Rust `Vec<Vec<f64>>`, and the Rust code could then just iterate that directly. It would even be made safe without the GIL as you'd implement your own mutex on the Rust side if you wanted it to be mutable. If you do this then it runs in about 1.2s on my laptop.
- deleted 5y ago[deleted]
- PartiallyTyped 5y agoThe culprit here is `a_list: Vec<Vec<f64>>`, which means the list of lists is copied as a whole, hence why the rust version is slower. In general, rust is at least as fast as C or C++, but this seems to be a special case.
- hobofan 5y agoIt's also repeatedly `push`-ing elements into the `Vec`, which will likely lead to repeated allocation and copying. In a case like this were you pretty clearly know the size beforehand you should crate a Vec of the appropriate size with `Vec::reserve`.
- Sukera 5y agoThe julia version also does that though
- yakubin 5y agoYou left out the explanation from your quote. The full is: > Rust is not that fast beacuse it needs to copy data; using Pyo3 objects would probably lead to similar results as cython, but with an added library. "It needs to copy data", because it's converting Python objects into Rust objects and back again. As the full quote states, it could be written in a different way, although that would make the code look really weird. In this benchmark there is also no normal C++ involved. There is only Cython using some C++ ints, but operating on Python lists. And that's not handled by writing C++ code that operates on Python structures, but writing Python code in Cython in a "C++ mode". Apples to oranges.
- volta83 5y ago> although that would make the code look really weird. Not it wouldn't? Using Pyo3 is arguably the most idiomatic way of interfacing Rust with python. The "added library" cost mentioned as an excuse not to use it is literally the 1 second it takes me to write `cargo add pyo3` . They didn't do this because it would make their claim that "Julia is the better language for extending Python" kind of moot. They started from the conclusion that they wanted to have, and worked their way backwards towards implementation and data that would support it.
- yakubin 5y ago> Not it wouldn't? I'll defer to your knowledge on that. I generally don't work with Python. I've only looked into interfacing C# with Rust and it looked crazy, so that's where I'm coming from. But yes, it's pretty clear that the results are more a consequence of what's in the source files than the languages used (if we can even say that e.g. C++ was used at all).
- hobofan 5y ago> interfacing C# with Rust AFAIK, there is no library that achieves a similar idiomatic interoperability between Rust and C#, like PyO3 for Python.
- remram 5y ago
- deleted 5y ago[deleted]
- mhh__ 5y agoYou mean C++. C usually comes out a bit worse because it gives even less information to the optimizer. And thanks to the GNU/LLVM monoculture most languages should perform roughly the same if they have them as a target e.g. I have found that D makes its it ridiculously easy to write highly specialized code that is both readable and visible to the optimizer (I will be blogging about that part later), but I'm sure I could force the same asm out from C++ or Rust etc.
- BiteCode_dev 5y agoI never considered Julia to extend python but it makes sense. Although as mentioned in the benchmark, with py2o, rust will be a superb contender, especially considering: - the toolchain is the easiest to setup - you can embed asm if you really need this extra juice
- deleted 5y ago[deleted]
- ZuLuuuuuu 5y agoI feel like the #1 downside of Python for the last few years is that you cannot take advantage of multiple cores of a CPU easily. Especially when you think it is heavily used in data analysis. We use Python for data analysis as well, and for 95% of operations we are doing, numpy is fast enough that we don't have any complaints. But sometimes, we do wish to be able to take advantage of all the cores in our CPUs, especially now that we can easily get an 8 core CPU for a reasonable price. There is multiprocessing module but you cannot share memory between processes. I guess the best options are either writing a C extension or using Numba. Writing C extensions require either distributing binary packages or C compiler to be present on the target computer which is not always ideal. So is using Numba the best solution currently? I tried it a bit in the past but the errors I got was a bit hard to interpret compared to regular Python errors. I wish the multithreading module had support for native threads. Is there any PEPs trying to bring easy multi-core support for Python?
- andi999 5y agoWhy not use the multiprocessing module? (if you can partition your problem space). I find memory sharing much more difficult to reliably implement an algorithm, and speed gain might be lower through cache conflicts.
- mhh__ 5y ago> cache conflicts This can be an absolute sinkhole for performance, but if you have a large dataset you shouldn't be sharing lines that much one would hope.
- andi999 5y agoThen you shd be able to do process parallelization.
- BlackFly 5y agoA real hard requirement is going to be resource pools such as session linked connection pools with associated data. But there are just plenty of places where you don't want to be forced into a message passing architecture for a service and shared memory may be a better fit.
- urschrei 5y agoRust doesn’t need to copy the data. It’s trivial to pass e.g. Numpy arrays to Rust as slices via Cython (let alone originating in Cython!), modify them, and return them, or use them as input for a new returned struct. https://github.com/urschrei/simplification https://github.com/urschrei/simplification https://github.com/urschrei/lonlat_bng https://github.com/urschrei/lonlat_bng https://github.com/urschrei/pypolyline https://github.com/urschrei/pypolyline Each of those repos has links to the corresponding Rust “shim” libraries that provide FFIs for dealing with the incoming data, constructing Rust data structures from it, and then transforming it back on the way out. As a more general comment, using a GC language as the FFI target from a GC language is begging for difficult-if-not-impossible-to-debug crashes down the line.
- volta83 5y agoTo add, it is trivial to guarantee that Rust code is "zero-copy", so if this is something you care about, Rust allows your program to fail to compile if it tries to make a copy.
- mhh__ 5y agoGodbolt example?
- deleted 5y ago[deleted]
- adwn 5y agoHere you go: https://play.rust-lang.org/?version=stable&mode=debug&edition=2018&gist=06d7335eef1551565c449c682b8ef2fe https://play.rust-lang.org/?version=stable&mode=debug&editio... Because the data structure LotsOfData doesn't implement the Clone trait, attempting to create a copy will fail at compile-time.
- mhh__ 5y agoOK. I was curious whether it was part of the type system or one of those #[feature] thingies. https://run.dlang.io/is/jOXfJz https://run.dlang.io/is/jOXfJz is a D equivalent
- short_sells_poo 5y agoI believe the toolchain of Rust is nicer in that you get a relatively small sized and self-contained rust library that can be easily distributed with a python package. Julia can't be easily bundled this way because you need to ship the entire runtime, with all the gubbins this entails. On the other hand: having used pyo3 to integrate rust with python in the past, the biggest pain is simply to reconcile the dynamism of python with AOT compiled rust code. There's a lot of noise at the interface from the large amount of type checking to unpack the specific types of numpy arrays coming across. Do you want to have your rust code work with all sorts of integer bit lengths? That'll be a code path for each. If you have a number of input types, have fun coercing them all. This can be alleviated with macros to a degree, but that just hides the problem really. Julia is JIT compiled, which means that there are no problems with types being determined only at run time. In fact, this one fact makes integration at the code level much nicer with Julia. So all in all it's a tradeoff really. I also found the tooling for Rust to be much more robust and stable (1 year ago admittedly).
- jakobnissen 5y agoI'm a massive Julia fanboy, but I would not extend Python with Julia if I could choose not to. Julia has a _massive_ runtime with a hello-world script consuming 150 MB of RAM, not to mention the dreaded startup-time. It's better and easier to use Julia as your main top-level "glue" language and call Python/Rust/C from Julia. Julia is in many ways a better glue language than Python - better multithreading, easier calling into C/Rust, etc. Then, over time, to the extent it is practical, you can replace foreign code with Julia code - because Julia is fast enough that it actually makes sense to do so. If you already have a large Python code base and can't switch the top-level language to Julia, I would just not use Julia for that project.
- galangalalgol 5y agoYou seem to understand Julia well. Is there a reason there is no nexus plugin or way to mirror the julia repo for dev networks that dont have unrestricted access to the internet, or are even airgapped. I see multiple people asking this online, so it is a common enough problem, but it seems like people are saying the Julia approach makes it hard to support.
- jakobnissen 5y agoI'm sorry, I have no idea, but I'm sure someone else has insight. Why would you want to mirror the Julia repo instead of just downloading already-compiled binaries? Do you want to maintain your own fork with your own set of patches?
- galangalalgol 5y agoFiguring the whole dependency tree without the package manager would be difficult and the build file sometimes has other side effects besides building for many packages. And downloading the binaries rather than letting the source go through nexus would be sidestepping most of the network security (and maybe legal) reasons people use nexus.
- ZeroCool2u 5y agoFrankly, Julia is a young language, so these things have yet to materialize. Also, for those that really do want it, the Julia package mirror is one of the few products that the company behind Julia sells as part of JuliaHub. It is inconvenient though that it doesn't fit in with the rest of the languages in Nexus.
- syzygyhack 5y agoWhy not Nim?
- jasfi 5y agoNim should be more popular, and I believe it will be in time.
- Gravityloss 5y agoI was a huge fan of Matlab way back. I wrote a hundred small Matlab programs for usage in the research department of the company I worked in. Doing data operations in Matlab was way more elegant than in say, Numpy which I tried later. Development was fast and ergonomics were good. After using Ruby for years, returning to Matlab style code in Julia felt somewhat awkward. Instead of my_array.length you have length(my_array). In my personal preference the method call is just a nicer way of doing the same thing. The single-instruction-multiple-data or dot notation sometimes worked and sometimes didn't, so you had to resort to loops anyway. % Julia a=[1 2 3 4] a.^2 log.(a) # Ruby a=[1,2,3,4] a.map{|element| element.pow(2)} a.map{|element| Math.log(element)} Ruby and map or each have their verbosity but overall it feels a more robust "hammer" for general programming tasks. On the other hand, Julia can be really dense and still easy to understand. Other languages of course take some of these things even further. Maybe some day I will find an ergonomics and nicety first successor to both. Plotting with two Y axes or generating histograms in Julia was also way harder than I remember it being in Matlab. Also manually having to load the file before every run to see the changes in action added a lot of overhead to the workflow. The workflow I was used to in Matlab involved very frequent making changes to code, running of the code that usually made some plot. Which is one command in Matlab, and plotting was fast, in 2001 already, on Windows NT 4. In Julia, you have to first load the modified file, then run it. Plotting takes a long time. One just can't get nearly as productive with it in 2021, compared to Matlab of 2001 vintage. What language would I pick if I had to do some quick analysis from some tables downloaded from the internet? Probably Julia still. If I had free access to Matlab, I would probably use it though.
- jhgb 5y ago> Instead of my_array.length you have length(my_array). In my personal preference the method call is just a nicer way of doing the same thing. Well...they're both method calls, aren't they? (So they're both the nicer way?)
- leephillips 5y agoThose who are attached to the class-based OOP model used, for example, in Python will find the dot notation more natural. But Julia’s multiple dispatch is a superset of this, and unarguably more powerful and flexible. Python OOP leads to monstrosities like >>> ', '.join(['1', '2', '3']) '1, 2, 3' >>> '1, 2, 3'.split(', ') ['1', '2', '3']
- syntonym2 5y agoI use python in a scientific context, but have so far not written much extensions for python in any of the languages tested. I'm interested in some guidance which language a) is easy to integrate with python and b) has some good performance, but this benchmark lacks the details to come to any conclusion. I tried to run the benchmark on my own computer, but the setup documentation was not enough for me to get the julia integration running. I haven't used julia before, so it might just be something very simple. Similar I haven't used poetry much before, and the given documentation failed to install the necessary setuptools-rust for me. I could fix it on my own, but doesn't make me feel certain about the outcome of the benchmark. The rust benchmark did not reproduce for me: "Rust (Pyo3) parallel after" has a 1.56 speedup for me, but a 2.6x slowdown for the author. Also I don't understand what the difference between "after" and "before" is, the code just calls the same code twice. Might be a JIT/Cache thing, but it's unclear to me. One sentence what before/after refers to would be very helpful. Generally all measurements are only done once. Measuring at least thrice gives one at least a chance to detect an outlier and gives possibility for statistics, e.g. is a difference betwee the different cython annotations even meanginful? The "C Cython (pure-python mode)" is reported faster then "C Cython (.pyx)". The Cython project itself says that using pyx files should be faster, so something strange is going on. "Cython is fast, but none of these methods are able to release the GIL. " (A) this is not true (B) this seems to be mostly over single threaded performance, so why is that meaningful? "Rust is not that fast beacuse it needs to copy data; using Pyo3 objects would probably lead to similar results as cython, but with an added library." The rust code already contains Pyo3, so an "added library" is not necessary as far as I understand. I'd guess the performance stems more from conversions between different types then anything else. Maybe Julia (and the python-julia bridge) is particular smart about it and thus it's super easy to use, while pyo3 (and cython) needs some more work to interface with python. Even if that is true, I couldn't say it from the presented data. With these caveats resolved I'd be interested in the benchmark, but without it I can't really say anything from it.
- jakobnissen 5y agoAbout two years ago (before I switched from Python to Julia), I was in the same boat as you. What I concluded was: 1) Calling into an actual static language like C or Rust is the best option. You get maximal performance and all the benefits of the static language. The downside is that you need to learn another language, and manage both languages in your project, including setup and compilation of the static language etc. 2) Cython is easiest for small-scale projects, since it integrates very well with Python, and you can learn it incrementally. But I found it annoying to work with - it felt like half a language that fell between Python and a proper static language. I ended up using Cython in the end, but I wasn't happy with it. 3) Numba looks interesting and promising. At least 2 years ago, it was too brittle and had too many situations where it didn't work or didn't give noticable speedups. I'm sure they improved it since then. I would definitely take a look. You can always just learn Julia of course and have this entire problem of "my high-level language is too slow" completely disappear ;)
- adsharma 5y agopy2many doesn't care which language is faster as long as the source language is annotated python3. I don't know if this particular benchmark transpiles correctly or not, but it should be possible to achieve much speedup if you annotate your python code properly. https://github.com/adsharma/py2many https://github.com/adsharma/py2many Looking for help with open issues.
- adsharma 5y ago./py2many.py --julia=1 list_py_annotations.py produces https://paste.ubuntu.com/p/vwxYkppRCw/ https://paste.ubuntu.com/p/vwxYkppRCw/
- boothby 5y agoThis is largely a test of cython, but why doesn't it use the cython features comparable to Julia's? Specifically, you can achieve parallelism with prange(..., nogil=True) and no-copy with cython.view.array. https://github.com/00sapo/cython_list_test/pull/2 https://github.com/00sapo/cython_list_test/pull/2
- dr_zoidberg 5y agoThe cython code is a bit messy. Changing from: cpdef float iterate_list(a_list): cdef double count = 0 cdef int i, j for i in range(len(a_list)): internal_list = a_list[i] for j in range(len(internal_list)): count += internal_list[j] print(count) return count To: cpdef float iterate_list(list a_list): cdef double count = 0 cdef double val = 0 cdef list ilist for ilist in a_list: for val in ilist: count += val print(count) return count Speeds up the iterate_list function an order of magnitude. On my PC: In [9]: %timeit list_cy.iterate_list(a_list) 1000000.0007792843 1000000.0007792843 1000000.0007792843 1000000.0007792843 1000000.0007792843 1000000.0007792843 1000000.0007792843 1000000.0007792843 385 ms ± 6.15 ms per loop (mean ± std. dev. of 7 runs, 1 loop each) In [10]: %timeit list_cyo.iterate_list(a_list) 1000000.0007792843 1000000.0007792843 1000000.0007792843 1000000.0007792843 1000000.0007792843 1000000.0007792843 1000000.0007792843 1000000.0007792843 2.71 s ± 182 ms per loop (mean ± std. dev. of 7 runs, 1 loop each) (yeah, I kept the prints that the code has) Where list_cy is the fixed code and list_cyo is the original code. Even then, iterating over a list of lists is _definitely not_ the optimal way you'd face a problem of this kind. Numpy arrays and memoryviews would be the correct tool to use.
- optimalsolver 5y agoThanks for this. Will Julia advocates every use honest benchmarks to make their language look good? I doubt it.
- dr_zoidberg 5y agoOP has an open pull request on his repo[0] where someone made basically this same change (different names for the variables, but same idea). According to OP, he tried this but it resulted in slower execution for him. I'm not sure he really followed what that PR says. In the case of the submitter, it says on their machine it gave a 2x speedup (smaller than my ~7x, but still significant). [0] https://github.com/00sapo/cython_list_test/pull/3 https://github.com/00sapo/cython_list_test/pull/3
- olliemath 5y agoPure python - under pypy - on my system faster than all of the above :D
- N1H1L 5y agoUntil Cython can handle real scientific stuff like FFTs, or curve fitting natively, it's useless and good only for toy problems.