[HN Gopher] Test for lists in Cython
___________________________________________________________________
Test for lists in Cython
Author : sapo
Score : 115 points
Date : 2021-04-19 10:01 UTC (13 hours ago)
(HTM) web link (github.com)
(TXT) w3m dump (github.com)
| jakobnissen wrote:
| I'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.
| O_H_E wrote:
| > _massive_ runtime ... 150MB of RAM, not to mention the
| dreaded startup-time.
|
| I understand how can this be inconvenient for me and you while
| scripting. But this is absolutely no problem for julia's
| currently main target market who runs multi-GB simulations.
|
| Also it is not like they will be spawning a julia instance in a
| hot loop (THAT would be horrible). You write some julia code,
| and import it using smth like pyjulia at the beginning of the
| file.
| [deleted]
| galangalalgol wrote:
| You 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 wrote:
| I'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 wrote:
| Figuring 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 wrote:
| Frankly, 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.
| galangalalgol wrote:
| Isn't julia hub still remote? You can't install it on an
| airgap or behindnyour firewall on your own equipment can
| you?
| ZeroCool2u wrote:
| I'm not sure honestly. We didn't end up buying it,
| because so few people use Julia in our org. Too much
| legacy matlab code and the professors in academia have
| yet to switch over.
| adsharma wrote:
| py2many 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
|
| Looking for help with open issues.
| adsharma wrote:
| ./py2many.py --julia=1 list_py_annotations.py
|
| produces
|
| https://paste.ubuntu.com/p/vwxYkppRCw/
| ur-whale wrote:
| From 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 wrote:
| rust has concept of references, maybe it was referring to its
| particular implementation of the problem
| efaref wrote:
| The 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]
| PartiallyTyped wrote:
| The 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 wrote:
| It'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 wrote:
| The julia version also does that though
| yakubin wrote:
| You 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.
| remram wrote:
| What is the "added library" though? PyO3 is already used to
| wrap, and is in fact the library doing the copy rather than
| providing a reference?
| volta83 wrote:
| > 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 wrote:
| _> 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 wrote:
| > interfacing C# with Rust
|
| AFAIK, there is no library that achieves a similar
| idiomatic interoperability between Rust and C#, like PyO3
| for Python.
| [deleted]
| mhh__ wrote:
| You 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.
| syntonym2 wrote:
| I 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.
| boothby wrote:
| Their .pyx implementation leaves much to be desired. Among
| other problems, the hot loop uses an untyped python list. Also,
| they're indexing into the list instead of iterating over the
| list. In creating the list, they're using .append() instead of
| list comprehensions.
|
| Fixing those minor issues cuts the runtime in half (see #3).
| Going for actual high-performance Cython (on my 12-core
| workstation) cuts the runtime by 20x (see #2).
|
| https://github.com/00sapo/cython_list_test/pull/2
| https://github.com/00sapo/cython_list_test/pull/3
| jakobnissen wrote:
| About 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 ;)
| syzygyhack wrote:
| Why not Nim?
| jasfi wrote:
| Nim should be more popular, and I believe it will be in time.
| sapo wrote:
| When considering python standard types and ease of use, such as
| lists and dicts
| boothby wrote:
| This 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
| BiteCode_dev wrote:
| I 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]
| ZuLuuuuuu wrote:
| I 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?
| CogitoCogito wrote:
| > 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.
|
| I understand the sentiment, but in this case I wonder if it is
| less a python issue and more an issue with numpy. I don't see
| any reason why numpy couldn't execute the last line of the
| following multi-threaded: >>> import numpy as
| np >>> a = np.arange(1000000000) >>> b = 2 \* a
|
| The same thing applies to matrix multiplication. I'm guessing
| numpy has chosen not to for good reason though.
|
| edit: Actually it seems numpy is multi-threaded under certain
| circumstances:
|
| https://stackoverflow.com/questions/16617973/why-isnt-numpy-...
| leephillips wrote:
| Numpy will perform a parallelized calculation when using one
| of the underlying BLAS routines, if you have the appropriate
| libraries installed on your machine. So to take advantage of
| your cores, try to write the calculation as a call to a
| linear algebra function. Loops or expressions with arithmetic
| operators as in your example will run on one core. Or, just
| use Julia.
| zozbot234 wrote:
| > I 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.
|
| Also a big downside with JavaScript. Of course both Python and
| JS are high-level interpreted languages where high-performance
| use cases aren't the foremost priority.
| willseth wrote:
| > Of course both Python and JS are high-level interpreted
| languages where high-performance use cases aren't the
| foremost priority.
|
| Modern JS engines are all primarily JIT-based, generally only
| interpreting code when it's faster than waiting on the JIT.
| JS JITs are quite good now, and in many cases will produce
| optimized C-equivalent compiled code from fairly naive JS.
| azangru wrote:
| > Also a big downside with JavaScript.
|
| Can't Node run multiple processes (or multiple workers, not
| sure of the proper terminology) on multiple cores? As
| discussed in this thread on StackOverflow:
| https://stackoverflow.com/questions/61893497/node-js-on-
| mult...
| zozbot234 wrote:
| > Can't Node run multiple processes
|
| Of course, but you can also run multiple Python processes.
| edgyquant wrote:
| It's way different though, node itself is multithreaded
| (just under the hood)
| pletnes wrote:
| Numpy operations release the GIL (usually at least) so you can
| use a threadpool and, indeed, share memory. Just try it and you
| may be pleasantly surprised.
|
| Dask is great if you're processing large amounts of data, and
| it recommends and supports threads for this reason.
| ZuLuuuuuu wrote:
| I didn't know that. So let's say I have 4 regular Python
| threads calling the same function, and in this function,
| let's say I call numpy.add on the same array (but different
| parts of the array), then will it actually use different
| cores for these 4 different threads? I will try it out, if it
| works, then that's actually great and would be super easy.
| pletnes wrote:
| Great, then my comment was worth it!
|
| Not sure there's anything to prevent you from foot-shooting
| if you're operating on the same array. I'd expect that this
| is only safe to do if, as you say, arrays are not
| overlapping. Let me know what you find.
| captain_price7 wrote:
| The answer is No, isn't it?
| andi999 wrote:
| Why 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.
| BlackFly wrote:
| A 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.
| mhh__ wrote:
| > 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 wrote:
| Then you shd be able to do process parallelization.
| mhh__ wrote:
| If you are genuinely hitting a wall like that surely it's time
| to move on from python? Or at least think about it
| ZuLuuuuuu wrote:
| The thing is that the current ecosystem (numpy + scipy +
| pandas + PyCharm) fits our company structure perfectly (very
| few software engineers, mostly test engineers who are not
| very proficient coders). And we already have tens of
| thousands lines of code. So changing the whole ecosystem just
| because 5% of the problems are slow is too big of a jump for
| us. For now, it is easier for us to write a bit of C code for
| places where we absolutely need speed. But it would have been
| so convenient if we could just have native threads, so that
| we don't even need that 5% C code.
| pantsforbirds wrote:
| You don't have to replace the core python parts. We moved a
| decent amount of our data pre-processing into Scala,
| Golang, and Data Base queries and then just store the
| processed data for the python models to use to train/run
| (which ends up using the super efficient C/Fortran/Cuda
| code).
| mhh__ wrote:
| Do you know what you are actually stalled on? i.e. Memory
| or Instructions, if it's the latter you can probably eek
| out some more performance just by fiddling with compiler
| flags (you'd be surprised simultaneously how clever and how
| utterly braindead some compilers are if you play with them
| on Compiler Explorer for a bit)
|
| Also, if you are running on Intel you might have some luck
| with Intel's profiling tools as, although I've never used
| them for python they are by far and away the best things in
| this area, they do support Python.
|
| I don't know what details they give you in from Python code
| directly, but if you've used perf to profile code at a low
| level before, vTune is like a Tom & Jerry shotgun in
| comparison.
| meepmorp wrote:
| > Compiler Explorer
|
| Never seen this before. Thanks!
| mhh__ wrote:
| https://xkcd.com/1053/
|
| Enjoy
| szubie wrote:
| Recent versions of Python (3.8+) have introduced a SharedMemory
| class for sharing memory between different processes (see https
| ://docs.python.org/3/library/multiprocessing.shared_mem...).
|
| The implementation may still be a bit buggy though, so use with
| caution! https://bugs.python.org/issue38119
| ZuLuuuuuu wrote:
| Thank you, I'll definitely check it out!
| anentropic wrote:
| There are also some libraries built on top of it that might
| be useful https://github.com/dillonalaird/shared_numpy
| ekianjo wrote:
| > Python for the last few years is that you cannot take
| advantage of multiple cores of a CPU easily.
|
| Same thing in the R ecosystem, it's possible to use multiple
| cores with the parallel package but there are caveats too.
| curiousgal wrote:
| I haven't run into any issues with that package honestly
| (mclapply ftw!). Other than not being able to use it on
| Windows of course.
| gbrown wrote:
| It's process based, so communication overhead is heavy and
| the infrastructure is minimal - if you're distributing jobs
| which are individually large it works fine, but it can't
| really be used to replace multithreading in a shared memory
| model.
| clircle wrote:
| I do as much data work as possible in data.table, which
| parallelizes things under the hood, and doesn't seem to
| require me to configure anything.
| jeremiecoullon wrote:
| I've been using JAX (https://jax.readthedocs.io/en/latest/) for
| scientific computing in general (in particular MCMC
| algorithms), as it's really fast. Even on a CPU you get massive
| speedups compared to numpy (can be up to 2 or 3 orders of
| magnitude faster in some cases).
|
| The main selling point of the library is automatic differential
| and compilation to XLA, but I've been using it even when I
| don't need gradients, as it's really fast (due to compilation).
| I also really like the random number generator as it's very
| good for reproducibility.
|
| I've played around with Julia in the past and really liked it,
| but in terms speed Jax has pretty much solved that problem for
| me
| PartiallyTyped wrote:
| Have you tried numba+numpy? In my experience, it is much
| faster than Jax and can compile to cuda. It's not caveat
| free, but it also removes the hustle of labeling arrays as
| donated in Jax.
|
| You may find this interesting https://github.com/scikit-
| hep/iminuit/blob/develop/tutorial/...
| cfcf14 wrote:
| Have you been successful in implementing non-trivial
| computational code in numba/numpy? I've always found it
| starts to really break for anything which isn't really
| trivial, and the errors are mostly non-prescriptive and
| highly verbose.
| grej wrote:
| We have a very large portion of production code written
| in numba we've been running for about 3 years and I made
| a small contribution to the library. There are a lot of
| gotchas to numba when the codebase gets large but the
| benefits far outweigh the downsides. I highly recommend
| numba.
|
| Edit: also note that a big part of the umap library is
| written in numba.
| craftinator wrote:
| I had the same experience about 2 years ago. Maybe it's
| changed since then? It was nice when you had a pure math
| function to write, but otherwise seemed to be unreliable,
| especially in multithreaded and multiprocess situations.
| PartiallyTyped wrote:
| I still have some issues in nojit mode, but jit is most
| of the time fine. It's still a bit iffy with lists, but
| most of the time I can use numpy arrays.
|
| TBF I am mainly using it for mostly pure path functions.
| joshuaellinger wrote:
| I just implemented both a CSV parser and an address
| standardizer in numba (both CPU and GPU) running in
| parallel feed through a message queue with a bunch of
| workers subprocs.
|
| It takes a bit of getting used to but the performance
| gains on impressive. Basically, my bottlenecks shift from
| compute to i/o.
|
| I think you have to balance it against writing in C/C++.
| Mentally, it is basically the same work as writing in C
| (you manage memory/you write complicated for-loops) but
| you have good array support with numpy. The primary
| advantage for me that everything stays in the python
| runtime environment. You just run the code without any
| extra steps.
|
| ...
|
| What is missing from the timing type 'toy' benchmarks is
| an understanding that there is typically more than one
| bottleneck in a real problem and it is easy to choose the
| wrong one to optimize and get little gains.
|
| After starting C (30 years ago now), spending a long time
| in C#, then switching to Python a few years ago, I think
| the unappreciated advantage of python is that I have to
| abandon all pretense of caring about speed and just get
| stuff working. It basically solves the pre-mature
| optimization problem for me by being a fast interpreted
| language rather than a slow compiled language.
| ZuLuuuuuu wrote:
| > I think the unappreciated advantage of python is that I
| have to abandon all pretense of caring about speed and
| just get stuff working. It basically solves the pre-
| mature optimization problem for me
|
| I feel the same way. With Python I just write the
| simplest algorithm that first comes to my mind, even
| though I know that it is not the most optimized way of
| doing things. But most of the time I am surprised that it
| works so fast that I realize I actually don't need to
| optimize it.
|
| And being able to create and easily manipulate
| dictionaries and tuples also allows me to create
| efficient data structures very quickly.
| jeremiecoullon wrote:
| I haven't tried numba but I've heard good things about it!
| Nice linked tutorial. If I understand correctly, you pass
| in jitted functions (using numba, and jax) into iminuit
| which does the optimisation?
|
| With Jax you can write native for loops that can also be
| jitted (I imagine you can also do this in numba?); this can
| then be really fast. Though in that case you would have to
| write the optimisation algorithm yourself which is not
| always practical!
|
| Another big speedup in Jax is due to vmap/pmap, which allow
| to vectorise/parallelise computation. For example you can
| build a massive gram matrix really quickly using vmap.
|
| Another point: Jax can also run on GPU (like numba :) )
| without having to rewrite anything.
| PartiallyTyped wrote:
| > I haven't tried numba but I've heard good things about
| it! Nice linked tutorial. If I understand correctly, you
| pass in jitted functions (using numba, and jax) into
| iminuit which does the optimisation?
|
| Yes, you can just pass the function and it runs the
| optimization, in severe cases, you can start by doing a
| grid `.scan` or `.simplex` (Nelder-Mead simplex method),
| then `migrad` to minimize, and `.hesse` for 1 sigma
| bound.
|
| You can also provide a gradient function that, well
| computes the gradient instead of computing it
| numerically.
|
| > I imagine you can also do this in numba
|
| Yes! It compiles to native code.
|
| > Another big speedup in Jax is due to vmap/pmap, which
| allow to vectorise/parallelise computation.
|
| It's possible to compile some vmapped functions with
| numba too, if the link is any indication, you may see
| even greater speedup than just jax.grad
|
| However, I do concur that jax's vmap is absolutely
| fantastic and I found it very useful on many occasions.
| jeremiecoullon wrote:
| Nice to know that numba has these features; I'll have to
| check it out at some point!
| killingtime74 wrote:
| Try Dask
| urschrei wrote:
| Rust 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/lonlat_bng
|
| 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.
| mhh__ wrote:
| > 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.
|
| When I was interfacing D code with a part of Unreal Engine that
| I think is garbage collected, I actually just took the L and
| copied everything into buffers on their malloc when handing
| stuff off to the engine. It wasn't particularly hot code so the
| memcpys were worth the peace of mind I found, ugly as it was.
| edenhyacinth wrote:
| Given that it's via pyO3, you could even pass the numpy arrays
| using https://github.com/PyO3/rust-numpy and get ndarrays at
| the other side.
|
| Same no copy, slightly more user friendly approach.
|
| Further criticism of the actual approach - even if we didn't do
| zero copy, there's no preallocation for the vector despite the
| size being known upfront, and nested vectors are very slow by
| default.
|
| So you could speed up the entire thing by passing it to
| ndarray, and then running a single call to sum over the 2D
| array you'd find at the other end. (https://docs.rs/ndarray/0.1
| 5.1/ndarray/struct.ArrayBase.html...)
| duckerude wrote:
| I'm also getting a 30% speedup simply from manipulating &PyList
| references instead of Vecs (without parallelism).
| whateveracct wrote:
| > 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.
|
| Safe interop between two GC'd languages (Haskell and Java) was
| one motivation of Haskell's new -XLinearTypes extension
|
| https://www.tweag.io/blog/2020-02-06-safe-inline-java/
|
| Generally, the new linear types are exciting but are nascent at
| the moment. But one extension to them (linear constraint [1])
| seems to allow embed the equivalent of Rust's ownership in
| Hsakell using more primitive features in the type system (to my
| understanding..)
|
| [1] https://arxiv.org/pdf/2103.06127.pdf
| [deleted]
| [deleted]
| [deleted]
| bjourne wrote:
| > 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.
|
| Not true!
|
| What you do is that you keep a registry for objects passed from
| the host vm to the foreign vm in which you register objects
| thus transferred. And you use a similar mechanism for objects
| passed from the foreign vm to the host vm. In CPython, you
| simply increment the refcount to prevent Python from collecting
| them prematurely.
|
| This is how Java does it (via JNI) and how many gc:ed runtimes
| interact with other gc:ed runtimes. This is how you do it in
| Rust too since Rust can't tell how long an object passed to a
| foreign vm is supposed to last.
| bennofs wrote:
| But it breaks down if you have cross-language cycles, because
| no garbage collector of either language will see the full
| cycle.
| zozbot234 wrote:
| > This ... is how many gc:ed runtimes interact with other
| gc:ed runtimes.
|
| It's not that easy. You'd need to register the object as a GC
| root as long as it's being managed by the foreign GC, and
| similarly ensure that objects in the foreign VM are properly
| "de-registered" when the local GC finalizes them. It's far
| from trivial, particularly when compared with other memory-
| management strategies.
| bjourne wrote:
| Yes, that is what the scheme I described is doing. You'd
| need a similar mechanism in Rust too to prevent it from
| collecting objects prematurely. I think implementing this
| is fairly trivial. The difficult part of writing an FFI is
| handling closures: Java calling Python calling Java calling
| Python which throws an exception...
| urschrei wrote:
| Thanks - this is the more informed version of what I wrote.
| chalst wrote:
| This is a solved problem: look at how the Lua FFI does it.
| Lua both is capable of exporting its own garbage collector
| to its counterparty and has a plug-in system for GC so that
| Lua can use the counterparty's GC. That allows you to
| either use Lua's GC for foreign objects or have the
| counterparty's GC manage Lua's objects.
|
| It's funny the hostility to JIT that some fans of compiled
| languages have.
| chalst wrote:
| There is no problem at all interfacing Julia's C API to Rust.
| It's a shame that C++ and Rust are bad fits together, but this
| actually strengthens the argument for Julia as the extension
| language rather than Rust, since Julia interfaces easily to not
| just C but also the C++ code in which much of the world's best
| numerical algorithms are written, and has an unrivalled FFI to
| Python, while Rust's object system is a poor fit for either
| Python or C++.
|
| > 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.
|
| <s>As for the FUD about interfacing JIT code to C/C++, this is
| a problem Julia was designed from the outset to tackle.
| Incidentally, Julia built on the excellent experience LuaJIT
| has had. A challenge: can you name a particular extension that
| would interface better from Rust than from Julia?</s>
|
| Oops, I misunderstood the criticism you were hinting at but
| failing to justify, sorry. OK, Julia already does interface to
| Python and this interface sees widespread use. If your
| suspicion is right, then where are the horror stories from
| people who were bitten when deploying code that built on the
| interface?
| urschrei wrote:
| > There is no problem at all interfacing Julia's C API to
| Rust
|
| Nobody claimed there was a problem?
|
| > As for the FUD about interfacing JIT code to C/C++
|
| I don't know what you're talking about. Sorry!
| chalst wrote:
| I misread your comment. I still consider what you wrote
| contained FUD, at least with respect to the last paragraph.
| volta83 wrote:
| To 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__ wrote:
| Godbolt example?
| [deleted]
| adwn wrote:
| Here you go:
|
| 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__ wrote:
| OK. I was curious whether it was part of the type system
| or one of those #[feature] thingies.
|
| https://run.dlang.io/is/jOXfJz is a D equivalent
| Nimitz14 wrote:
| one 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 wrote:
| I concur, pybind11 is the smoothest Python extension story I've
| experienced so far.
| dr_zoidberg wrote:
| The 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 wrote:
| Thanks for this.
|
| Will Julia advocates every use honest benchmarks to make their
| language look good? I doubt it.
| ChrisRackauckas wrote:
| Note that the OP is a Python user, not a Julia user. The
| Github profile is a bunch of Python packages and the Julia
| code wasn't even optimized
| (https://github.com/00sapo/cython_list_test/pull/5). If this
| test says anything, it at least would say that a
| inexperienced Python user could pick up Julia and do pretty
| well, even if the code they write isn't great. I think the
| right thing to do is just to help this guy learn to code for
| performance a bit better: it'll be better for him and would
| bring some positivity.
|
| Even if it doesn't say that, bashing people who use Julia for
| a repository made by a Python user is a new level of HN
| trolling.
| dr_zoidberg wrote:
| OP 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
| anotheranonym wrote:
| 1000% this. Multiple times I've encountered a Julia benchmark
| claiming to show its superiority in a task I routinely
| perform. And every time the pro Julia benchmark turned out to
| be total BS.
| eigenspace wrote:
| It's hard to say anything about your use-case without more
| information, but I will say that one thing is that the way
| people talk about julia often seems to give people a
| mistaken impression about how to attain it's performance
| claims.
|
| Namely, realizing these claims requires learning the
| language, and actually taking advantage of it's strengths
| rather than just writing 'python in julia'.
|
| It's very common for Python users to show up on the
| Discourse forum and complain that julia is slower than
| python and then show some code that's basically just Python
| code written in julia, including a huge proliferation of
| global variables, allocating huge amounts of temporary
| arrays, etc.
|
| There's also a huge spectrum of 'benchmark quality' out
| there. E.g. the benchmarks this HN post features seem
| pretty shitty and are not measuring anything interesting or
| useful as far as I can tell.
| neolog wrote:
| I wonder if it's more common for that language than other
| languages. Probably not many people have enough expertise
| in many different languages to compare fairly across them.
| Gravityloss wrote:
| I 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.
| vchuravy wrote:
| Broadcasting and map are two different operations. If all the
| inouts have the same shape broadcast is equivalent to map, but
| in Julia you can also just use map.
|
| `map(el->el^2, a)`
|
| and inspired by Ruby
|
| ``` map(a) do element log(element) end ```
|
| The latter being syntax sugar for the former.
| leephillips wrote:
| The standard way to make a histogram in Julia is
| histogram(data)
|
| Using the latest version (1.6 - although 1.6.1 just came out)
| the time to first plot is just a few seconds. After that,
| plotting in the REPL is instantaneous.
|
| I probably don't understand what you're getting at when you
| speak of making frequent changes to code. REPL-based
| development in Julia is excellent, and there are Pluto
| notebooks as well.
| Gravityloss wrote:
| The way I would like to work is to have the repl open on the
| left hand side of the screen and code editor (like Sublime
| text, I'm sure many use Vim) on the right hand side.
|
| I would run the code in repl (just using up-arrow and enter),
| get some plot, modify the code in the editor, save it, and
| rerun it repl.
|
| Repl is used for connecting inputs to the program, not for
| editing. Often times you want to develop some small program
| that is part of a bigger program. So you run the big program
| up to the point it calls the small program. Or you run with
| some mock data. Then you have all your inputs for the small
| program, so you can start iterating it. Run - observe - edit
| - run - observe - edit.
|
| In Matlab this worked because it detected if the file had
| changed, and reloaded it. You didn't specifically have to
| call load("my_function.m") before every call to it. This
| means you can just lift code from the big program and iterate
| on it in repl.
| rsfern wrote:
| Check out Revise.jl, it enables this workflow, and it's
| awesome.
|
| https://docs.julialang.org/en/v1/manual/workflow-
| tips/#Revis...
| leephillips wrote:
| Also, my Julia REPL is in a term buffer in vim, and I
| communicate with it (in both directions) from my editing
| buffer using the vim-sendtowindow plugin.
| [deleted]
| jhgb wrote:
| > 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 wrote:
| Those 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']
| deetsb wrote:
| As I'm clearly missing the bigger picture, what do you find
| to be super offensive about this?
|
| I read the first as the concat symbol applied to an
| iterable leads to string concated by the concat symbol.
|
| I read the second as an iterable broken by split symbol
| leads to an iterable of the chunks.
| leephillips wrote:
| It's hard to remember what order to put things in,
| because the two methods follow opposite conventions. And
| that's because this type of OO design has no obvious
| method of organization.
|
| In the first case, I want to do something to a list: join
| it into a string. So, clearly, I need a list method? But
| no, I need to engage in some form of indirection; for
| some reason, I need to reach for a _string_ method. Even
| if there is _no string_ that I want to use as a
| delimiter. In that case, I need to use a string method on
| an empty string.
|
| OK, I'll play along. Now I want to take the string and
| split it into an array. Now that I've been educated, I
| know better than to try the sensible thing. Pre-
| enlightenment, I would have reached for a method applied
| to the thing that I wanted to transform. But now I know I
| should think backwards, and use a method applied to the
| delimiter. OOPs.
| leephillips wrote:
| Here it is in Julia: julia> join([1 2
| 3], ", ") "1, 2, 3" julia>
| split("1, 2, 3", ", ") 3-element
| Vector{SubString{String}}: "1" "2"
| "3"
|
| Note how they are both functions. The data that they
| operate on is the first argument, in both cases. The
| _optional_ second argument is the obvious next most
| important thing, the delimiter. Other optional arguments
| come after that. There is nothing to remember, because it
| makes sense.
| diarrhea wrote:
| The Python FAQ itself adresses this:
| https://docs.python.org/3/faq/design.html#why-is-join-a-
| stri...
|
| You'll have to decide if you find that convincing. I
| understand your point and kind of wish I hadn't read this
| thread because I'm more torn than before.
| leephillips wrote:
| Not convincing exactly, but I'm glad to know the thinking
| behind it, thank you. How are you torn?
| deetsb wrote:
| That's fair Lee. I think I'd accidentally stumbled upon
| the logic for .join acting on the separator rather than
| the iterable, but I do agree it's awkward unless you're
| trying to figure out why it is (which is poor design),
| and certainly not how most people think, especially with
| the inconsistency of split not acting on the separator.
| jhgb wrote:
| Multiple dispatch is usually class-based as well.
| leephillips wrote:
| Not in Julia.
| jhgb wrote:
| It doesn't have named types on which methods get
| dispatched?
| leephillips wrote:
| Yes, but those are not classes, in the sense of Python or
| other OOP language classes.
| jhgb wrote:
| Named types that can be dispatched on _are_ classes, see
| CLOS. Hell, even in Python, classes are pretty much the
| named types that you can dispatch on, although of course,
| in Python, you generally can 't create different kinds of
| types that can't be dispatched on like types satisfying a
| predicate - at least to my knowledge you can't, so the
| difference is somewhat less tangible there.
| leephillips wrote:
| In Julia they are not classes in the sense that the term
| is normally used in, for example, Python. Classes bind
| together data and methods, which is what leads to all the
| problems. In Julia functions are generic, and specialized
| to methods acting on _arbitrary_ combinations of types;
| they are freed from the data.
|
| https://arstechnica.com/science/2020/10/the-unreasonable-
| eff...
| jhgb wrote:
| > Classes bind together data and methods
|
| Not in CLOS, from which Julia's mechanisms are derived
| (which leads to the question why not CLOS but Python of
| all things should be used as a source of "normal sense"
| of anything Julia-related). In CLOS, classes bind
| together data, generic functions name abstract
| operations, and methods represent specific code that
| deals with implementing a generic function for a
| particular combination of type arguments.
|
| > which is what leads to all the problems
|
| I don't think anyone is disputing that here. There's a
| reason why CLOS didn't do any of that.
|
| > In Julia functions are generic, and specialized to
| methods acting on arbitrary combinations of types; they
| are freed from the data.
|
| ...yes, just like in CLOS (unsurprisingly, given Julia's
| heritage), and those types are effectively CLOS classes.
| leephillips wrote:
| Well, it seems as if we were just at cross purposes due
| to terminology.
|
| Although multiple dispatch in CL (and Perl) predates
| Julia, I was not aware that Julia's design derived from
| it. Do you have a reference that traces this?
| jhgb wrote:
| I find it virtually certain that it does derive from it.
| Considering that one of the authors of Julia wrote
| Julia's front-end in Lisp (https://github.com/JuliaLang/j
| ulia/blob/master/src/julia-par... and some other files in
| the same directory), it would have been astonishing for
| CLOS to not have major impact on the design. There's also
| some relevant statements in a paper on Julia's design
| (https://dl.acm.org/doi/10.1145/3276490) in the part on
| multiple dispatch in section 7. Related Work, where CLOS
| and its "algebraic cousin" Dylan are mentioned. I got the
| impression that Julia's object system is basically CLOS
| without quite a few of CLOS' complexities such as
| inheritance (which in CLOS necessitates some advanced
| extension facilities to cover some corner cases if method
| lookup doesn't do what you want it to do if you're
| attempting a highly complex application model). The nice
| effect of those feature removals was that in many cases
| monomorphization of call sites in emitted native code is
| possible, which is presumably the other reason for those
| feature removals: suddenly even primitive operations such
| as +, * etc. can be generics without incurring (most of
| the time) dispatch cost at runtime. That (primitive
| operations being generic functions) is not the case in
| CLOS, although that can also very well be attributed to
| backwards compatibility efforts in Common Lisp.
|
| Interestingly enough, in Julia's documentation, the
| section "Noteworthy Differences from other Languages"
| (https://docs.julialang.org/en/v1/manual/noteworthy-
| differenc...) compares Julia to only several relevant
| languages, which are: Matlab, R, Python, C/C++,
| and...Common Lisp, of all things. I very strongly doubt
| that this is a coincidence.
| leephillips wrote:
| Thank you for that interesting reply.
| nostoc wrote:
| That's not a failure of OOP, but a failure of design.
|
| This could have easily been avoided if `join` would have
| been a method of the list object, not the string object.
| But we're stuck with this now.
| duckerude wrote:
| If join were a method on lists then you wouldn't be able
| to use it with other iterables. Putting it on str is more
| useful, if more confusing.
|
| Maybe it would be better as a standalone function. But
| then you'd either have to import it or it would pollute
| the global namespace.
|
| Other OOP models offer different solutions.
| leephillips wrote:
| The Julia join function works on any iterable whose
| members are or can be converted into strings.
| adgjlsfhk1 wrote:
| That requires multiple dispatch.
| short_sells_poo wrote:
| I 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).
| olliemath wrote:
| Pure python - under pypy - on my system faster than all of the
| above :D
___________________________________________________________________
(page generated 2021-04-19 23:01 UTC)