Enjoying the discussion put forth by @Mo8it, as one can always learn something from this kind of comparisons.
The “happy path” in Julia seems to be to just pre-allocate outside of hot loops. Reusing the same memory addresses over and over will hopefully make the memory stay in the cache (which in modern CPUs can be in the range of 32-96MB for L3).
In my experience, the kind of “low-level Julia” code that you write (which is admirable) is simply too hard to write, as fighting the GC is not easy at all.
But the “happy-path” I just mentioned is something that I found I also had to do in C++ when using so-called zero-cost abstractions there like std::vector
. std::vector
is way slower than C-style stack-allocations unless you pre-allocate, as I found out in my test here.
After running this test, I stopped worring too much about C and stack allocations and came to the conclusion that the happy path of pre-allocation in Julia would give me nearly 100% performance when compared to C++. I haven’t tested the same kind of thing with multithreaded code, but I suspect the conclusion would be similar.
That being said, I believe the OP point on performance is related to the fact that if anyone is having any meaninfull time spent on GC, they are just not pre-allocating (not in the happy path).
Rust’s compiler, on the other hand, is known to beat you into submission in order to make you stay in Rust’s happy path. I belive that ownership semantics allow them to allocate on the stack safely, so I would say there is a point there to consider. Maybe Rust’s default stack-allocation is a nice feature indeed (so, in comparison to Julia we would avoid problems with too many allocations), but the cost is that we should have to submit to the borrow checker, so I’m not sure if it’s a good trade-off.
I think I would personally prototype code that allocates a lot a makes my coding fast, then optimize it when stable, than to take a large ammount of time trying to please a very strict compiler.
Another point to throw into the discussion that so far hasn’t been mentioned is compilation times. From the funny video Interview with senior Rust developer I get that compilation times in Rust are very slow. It seems it could be even worse than C++ (especially now that C++ introduced modules to reduce compilation time). Considering that the most annoying problem with Julia was actually not having a GC but “time-to-first-plot” (well on in it’s track to being resolved) this is actually what really puts me off with Rust. Slow compilation times, plus a hard-to-use compiler… too much to cope with for me, for little to no percievable gain.
Other points of relevance that haven’t been mentioned: how mature is GPU computing in Rust? For what I see here not so much. And arguably the GPU has and will have a growing importance in scientific computing, so Rust seems to be really lagging in a critical area. It makes me wonder if the language is even well designed to be run on a GPU. For a borrow-checked language with primary support of GPU computing, we will have Mojo, btw.
A final point. The worst bugs I had to deal with so far doing scientific computing were definitely not “code” bugs, but rather nasty “off by one” or similar bugs, that a compiler simply won’t catch as it cannot understand what I’m actually trying to compute. The only systematic way that I found of dealing with such bugs was to introduce abstractions that forced me into certain specific restrictions. In Julia, which is generic by default, we can introduce any restrictions to our code by making functions take some abstract type instead of being fully generic, or by enforcing that some method is implemented on a given type with a simple @assert. I’d rather go with this phillosophy: generic by default, and where I am able to introduce restrictions whenever I see fit for my own “off-by-one” safety and similar issues.