Live data from Hacker News

The humble for loop in Rust

blog.startifact.com

41–50 of 60 posts

Re: The humble for loop in Rust

#41

Earlier quoted context omitted.

That surprises me, and I'd expect the FP version to be at least as fast, with the option to be much faster. With the for loop, you're saying "run against this value, then run against the next one, then run against the next one, then...". If the compiler isn't certain that the iterations aren't free of side effects, then it would have to run each one in order before moving on to the next. `.map(...)` implies "I don't…

> `.map(...)` implies "I don't care about ordering, and therefore you don't need to, either" In plenty of languages, no such implication exist. `map` can be specified to run in a certain order.

Then that may not be an appropriate optimization for those languages.

Re: The humble for loop in Rust

#42
post #4

> Why is map so much faster? I am not sure. I suspect with the map() option the Rust compiler figures out it can avoid allocations altogether by simply writing over the original vector, while with the loop it can't. Or maybe it's using SIMD? I tried to look in the compiler explorer but I'm not competent enough yet to figure it out. Maybe someone else can explain! Yep, it's due to SIMD -- in the assembly for `using_ma…

SIMD is true, but the original guess is correct, and that effect is bigger! using_map is faster because it's not allocating: it's re-using the input array. That is, it is operating on the input `v` value in place, equivalent to this: pub fn using_map(mut v: Vec ) -> Vec { v.iter_mut().for_each(|c| *c += 1); v } This is a particularly fancy optimization that Rust can perform.

Even when passing the array as a borrow instead of a clone[1], map still auto-vectorizes and performs the new allocation in one go, avoiding the bounds check and possible calls to grow_one

[1] https://godbolt.org/z/K9z6PvdYh

Re: The humble for loop in Rust

#43

Yeah I kind of wish there was a way to still use `?` inside map/filter/etc. lambdas. Error handling with that functional style is generally way more awkward than for loops, but also it's often more elegant in other ways (e.g. Rayon). I think Ruby has some kind of feature that works like that but IIRC it looked less foot-gun more foot-bazooka. Does anyone know of any languages that solve that problem elegantly?

I think what you want is a short-circuiting behavior. I don't see a problem with the status quo, if map is able to short-circuit, that means that it runs in order and have side effects. If your "map" can cause the whole function to return, then just write a for-loop. I don't even know if it should return from the whole function or just stop the mapping?

Re: The humble for loop in Rust

#44

Earlier quoted context omitted.

That surprises me, and I'd expect the FP version to be at least as fast, with the option to be much faster. With the for loop, you're saying "run against this value, then run against the next one, then run against the next one, then...". If the compiler isn't certain that the iterations aren't free of side effects, then it would have to run each one in order before moving on to the next. `.map(...)` implies "I don't…

> `.map(...)` implies "I don't care about ordering, and therefore you don't need to, either" In plenty of languages, no such implication exist. `map` can be specified to run in a certain order.

What languages would that be?

Re: The humble for loop in Rust

#45
post #37

Earlier quoted context omitted.

Streams (FP in java) is slower than for loops for a couple reasons. A big one is java doesn't natively support real closures or lambdas. It does have syntax for them, but that is just syntactic sugar for an class with a single method under the hood. So streams end up doing lot of object allocation and garbage for the fake closures. Also, streams operate on objects, so they have to be on the heap. You can't use them w…

Huh, interesting. Thanks for that! Today, I learned.

[deleted]

Re: The humble for loop in Rust

#46
Dear author (and others who writing things like this): it looks you put in considerable time on code comparisons and benchmarks. Why not publish the source code for them? Benefits include:

1. The comparisons, as you've written them up, probably will get stale fast. It would be nice to be able to re-run them.

2. Some of the examples are surprising. Why? Any bugs? Some kind of weirdness? Readers would want to explore.

3. Readers can see how you did the benchmarks. We can put more/less stock in them. Hopefully you used `criterion` or similar, with warm ups, etc.

Re: The humble for loop in Rust

#47
post #44

Earlier quoted context omitted.

> `.map(...)` implies "I don't care about ordering, and therefore you don't need to, either" In plenty of languages, no such implication exist. `map` can be specified to run in a certain order.

What languages would that be?

Probably most that has imperative stuff or closures. Here's the spec from ECMAscript. https://tc39.es/ecma262/multipage/indexed-collections.html#s...

C# and Javascript are the ones that I know of for sure.

Re: The humble for loop in Rust

#48

Yeah I kind of wish there was a way to still use `?` inside map/filter/etc. lambdas. Error handling with that functional style is generally way more awkward than for loops, but also it's often more elegant in other ways (e.g. Rayon). I think Ruby has some kind of feature that works like that but IIRC it looked less foot-gun more foot-bazooka. Does anyone know of any languages that solve that problem elegantly?

What's the ruby feature you're referring to?

https://medium.com/swlh/returning-from-a-ruby-proc-beware-of...

Re: The humble for loop in Rust

#49
post #37

Earlier quoted context omitted.

That surprises me, and I'd expect the FP version to be at least as fast, with the option to be much faster. With the for loop, you're saying "run against this value, then run against the next one, then run against the next one, then...". If the compiler isn't certain that the iterations aren't free of side effects, then it would have to run each one in order before moving on to the next. `.map(...)` implies "I don't…

Streams (FP in java) is slower than for loops for a couple reasons. A big one is java doesn't natively support real closures or lambdas. It does have syntax for them, but that is just syntactic sugar for an class with a single method under the hood. So streams end up doing lot of object allocation and garbage for the fake closures. Also, streams operate on objects, so they have to be on the heap. You can't use them w…

> It does have syntax for them, but that is just syntactic sugar for an class with a single method under the hood. So streams end up doing lot of object allocation and garbage for the fake closures.

In Rust a closure is really just a struct that implements up to three closure traits, each of which provide a single function. So from that side of things, what Java is doing for them isn't inherently different from Rust.

Re: The humble for loop in Rust

#50
post #37

Earlier quoted context omitted.

That surprises me, and I'd expect the FP version to be at least as fast, with the option to be much faster. With the for loop, you're saying "run against this value, then run against the next one, then run against the next one, then...". If the compiler isn't certain that the iterations aren't free of side effects, then it would have to run each one in order before moving on to the next. `.map(...)` implies "I don't…

Streams (FP in java) is slower than for loops for a couple reasons. A big one is java doesn't natively support real closures or lambdas. It does have syntax for them, but that is just syntactic sugar for an class with a single method under the hood. So streams end up doing lot of object allocation and garbage for the fake closures. Also, streams operate on objects, so they have to be on the heap. You can't use them w…

The big differences are: 1. Rust closures are by-value structs; whereas Java closures are heap objects. 2. Rust generics are monomorphized; whereas Java type-erases them -> lots of virtual call overhead when passing a closure to a generic function.

Sometimes, if the Java JIT manages to inline absolutely everything, it can optimize away these overheads. But in practice, Rust FP gets optimized a lot more reliably than Java FP.

Post reply on HN