From 024d42a6116681dae3528c51184ac41d6d2550c5 Mon Sep 17 00:00:00 2001 From: Jishnu Bhattacharya Date: Sat, 9 Nov 2024 18:41:07 +0530 Subject: [PATCH] Loop over `Iterators.rest` in `_foldl_impl` (#56492) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit For reasons that I don't understand, this improves performance in `mapreduce` in the following example: ```julia julia> function g(A) for col in axes(A,2) mapreduce(iszero, &, view(A, UnitRange(axes(A,1)), col), init=true) || return false end return true end g (generic function with 2 methods) julia> A = zeros(2, 10000); julia> @btime g($A); 28.021 μs (0 allocations: 0 bytes) # nightly v"1.12.0-DEV.1571" 12.462 μs (0 allocations: 0 bytes) # this PR julia> A = zeros(1000,1000); julia> @btime g($A); 372.080 μs (0 allocations: 0 bytes) # nightly 321.753 μs (0 allocations: 0 bytes) # this PR ``` It would be good to understand what the underlying issue is, as the two seem equivalent to me. Perhaps this form makes it clear that it's not, in fact, an infinite loop? --- base/reduce.jl | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/base/reduce.jl b/base/reduce.jl index 952d71bb2a849..6ceb76089d59c 100644 --- a/base/reduce.jl +++ b/base/reduce.jl @@ -43,15 +43,15 @@ function foldl_impl(op::OP, nt, itr) where {OP} end function _foldl_impl(op::OP, init, itr) where {OP} - # Unroll the while loop once; if init is known, the call to op may - # be evaluated at compile time + # Unroll the loop once to check if the iterator is empty. + # If init is known, the call to op may be evaluated at compile time y = iterate(itr) y === nothing && return init v = op(init, y[1]) - while true - y = iterate(itr, y[2]) - y === nothing && break - v = op(v, y[1]) + # Using a for loop is more performant than a while loop (see #56492) + # This unrolls the loop a second time before entering the body + for x in Iterators.rest(itr, y[2]) + v = op(v, x) end return v end