Raw Loops for Performance?
sandordargo.com
sandordargo.com
Response response;
response.data.reserve(widget.data.size());
std::ranges::transform(widget.data, std::back_inserter(response.data),
[](const auto& element) {
return ToData{.title = element.title, .amount = element.amount*42};});
return response;Is it, though? back_inserter should replicate push_back(), which incurs a move at the very minimum. The approach with emplace_back() does not.
https://quick-bench.com/q/1zwemArsS0gtKwveR9FZsuRwT34
This also isn't the way C++20 wants us to work with ranges and views, either -- this is basically just std::transform as you'd write it 20 years ago, except with a container as the argument instead of your begin / end iterators (so, basically absl::c_transform).
ETA: there is a slight advantage for your function body using GNU libstdc++, but there is no advantage when using LLVM libc++. Under libc++ all three are exactly as fast, and faster across the board than any of the GNU results.
As a team you're going to drown in C++ if you let everyone add their favourite coding conventions, standards or libraries to the code - it's the worst language for that sort of sprawl.
It's not even that I disagree with his premise, in the abstract, and if he were including this as part of a larger change in that area of code, I could see it being reasonable. But as a "I don't like the way this code looks so I'm going to rewrite it" PR, no.
plus I'm now getting old enough to realize early retirement might be possible. That means I need to make sure new people understand my code so I'm not called back inside from my (whatever my next life is)
just rewriting it because isn't good. However rewriting it in a more expressive way is better.
often manual loops have unnoticed off by one errors as well so the above exercise can fix weird bugs nobody has figured out.
Not cleaning things up as you go is why many projects have declared bankruptcy and done a billion dollar rewrite. We learn things over time, but if you don't apply the lessons you end up with really bad code.
OTOH, just changing everything all the time isn't right either. Find the correct balance so your code is constantly improving and as a result maintanable for centuries.
I think I agree that, in projects dominated by C++ or similar languages, I would be less positive about it, just because of how complicated they can get.
That further eliminates the copies outside of the loop. At that point, you basically have created every element just once, with no moves or copies.
Meanwhile, the ranges approach incurs a move followed by a copy for every element. It's not obvious to me how to eliminate those.
That would be catastrophic, since the view is only storing a bunch of references to the (Widget widget) argument that gets deleted once the function returns. In general, you have to be wary regarding how long references are valid for.
Anyway, I'm not entirely sure what the purpose of the transformed_data vector is. If you're already going to put it into response.data, just clear it out and start adding elements directly. Or if that's infeasible, at least move it into response.data instead of copying it.
> I'm not entirely sure what the purpose of the transformed_data vector is.
I suppose that in the real application, they want something like 'response = f(g(h(...(input)...))', where f . g . h is some non-trivial composition of a bunch of view transforms. So then it kinda makes sense. Basically, construct a lazy list, then eval everything at the end and store the result somewhere.
If so, then that 'f . g . h' composition might compile to something faster than the equivalent of 3 loops and vectors for intermediate results, since the compiler can fuse the whole thing into a tight loop. They might have made the benchmark misleading by constructing a too-minimal example.