Code change:
Today for no reason in particular I was curious about the behaviour of arrays when zipped together and then mapped into a single array, roughly: I wrote some tests for this and disassembled them in Godbolt to see what was going on behind the scenes and wow was a whole lot going on that seemed like it didn't need to be. So I wrote something kinda like what's in the stdlib for mapping and zipping arrays, just without the iterators, to see if I could do any better. Turns out I absolutely can: Maybe the design isn't as optimal (or as safe) as it could be, but it performs perfectly well in benchmarks (read: as fast as an unguarded version and anywhere between 3-25 times faster than the definition shown at the top of this issue. Here's a rough table of execution times averaged over the primitive ops (, , &|^`): ! Not really liking how things are looking for . So, question then is: is there something in Rust that needs to change to let these optimisations happen, or do I need to look at putting this in a library somewhere (or maybe stdlib wink wink nudge nudge?)? *The 25x comes from the benchmarks. benches at 11.230ns, and benches at 452.593ps, which seems suspicious but I can't seem to find anything wrong with the result.
I suspect the issue here is that has the wrong shape. It returns but vector instructions generally want . isn't an iterator type that describes the desired iteration and then performs it once, rather it's one loop that creates an intermediate result and then a second loop that produces the final output. Perhaps we could massage the code into a shape that the optimizer can disentangle, but I think it would be better to offer something like your , albeit with a nicer name.
This is missing an actual reproducer... Tried a few sizes here: I assume the report is about the case where this doesn't get unrolled, like with N=128?
I'm not entirely sure what you're looking for in a reproducer, but I have a benchmark program set up using to check out the performance of the various functions (see the "Benchmark" section at the bottom of this post). More or less I have as shown above (renamed to ), and both of these: Then I set up a lot of benchmarks. These test the speed of some primitive operations (, , , , , ) on arrays of lengths 8, 16, 32, 64, 128, 256, and 512 for types u8, u16, u32, and u64. This gives a total of 168 benchmarks per function, which reduces down to 28 per test after the primitive tests are averaged for each function, length, and type. The results of this are: ! This was run on the latest nightly as of 2022-12-21. I think this is pretty hard evidence that performance using is not where it should be. and are grouped so close together in the bottom section of the graph you can't visually see the difference. But for ? It's very obviously slower than the other two. Notice how it takes more time to map together two than it takes the other methods to map together two . Even if we just look at shorter inputs like was suggested, it doesn't look good for : ! These are just the results for lengths up through 64, and we can pretty clearly see that is just always slower. <details<summary<strongBenchmark</strong</summary<p: </p</details
Part of this is the classic "too many copies" trio But some of it is probably just that, being eager, it won't optimize unless it fully unrolls.

Related discussion:
text: c } <del> // CHECK-LABEL: @auto_vectorize_array_zip_map </del> <ins> // CHECK-LABEL: @auto_vectorize_array_from_fn </ins> #[no_mangle] <del> pub fn auto_vectorize_array_zip_map(a: [f32; 4], b: [f32; 4]) -> [f32; 4] { </del> <ins> pub fn auto_vectorize_array_from_fn(a: [f32; 4], b: [f32; 4]) -> [f32; 4] { </ins> // CHECK: load <4 x float> // CHECK: load <4 x float> // CHECK: fadd <4 x float> // CHECK: store <4 x float> <del> a.zip(b).map(|(a, b)| a + b) </del> <ins> std::array::from_fn(|i| a[i] + b[i]) </ins> }