From fb89009b655261ef45b521df40f7def83b5bd482 Mon Sep 17 00:00:00 2001 From: Takafumi Arakaki Date: Thu, 10 Oct 2019 18:49:58 -0700 Subject: [PATCH 01/10] Use map and filter transducers in foldl as an optimization --- base/reduce.jl | 109 +++++++++++++++++++++++++++++++++++-------------- 1 file changed, 79 insertions(+), 30 deletions(-) diff --git a/base/reduce.jl b/base/reduce.jl index 425d8d6f28f2f..d4b7402fa7d45 100644 --- a/base/reduce.jl +++ b/base/reduce.jl @@ -36,31 +36,80 @@ mul_prod(x::Real, y::Real)::Real = x * y ## foldl && mapfoldl -function mapfoldl_impl(f, op, nt::NamedTuple{(:init,)}, itr, i...) - init = nt.init +mapfoldl_impl(f, op, nt, itr) = foldl_impl(op, nt, Generator(f, itr)) + +function foldl_impl(op, nt, itr) + op′, itr′ = _xfadjoint(BottomRF(op), itr) + return _foldl_impl(op′, nt, itr′) +end + +function _foldl_impl(op, nt, itr) + init = get(nt, :init, _InitialValue()) # Unroll the while loop once; if init is known, the call to op may # be evaluated at compile time - y = iterate(itr, i...) + y = iterate(itr) y === nothing && return init - v = op(init, f(y[1])) + v = op(init, y[1]) while true y = iterate(itr, y[2]) y === nothing && break - v = op(v, f(y[1])) + v = op(v, y[1]) end + v isa _InitialValue && reduce_empty_iter(op, itr, IteratorEltype(itr)) return v end -function mapfoldl_impl(f, op, nt::NamedTuple{()}, itr) - y = iterate(itr) - if y === nothing - return Base.mapreduce_empty_iter(f, op, itr, IteratorEltype(itr)) - end - x, i = y - init = mapreduce_first(f, op, x) - return mapfoldl_impl(f, op, (init=init,), itr, i) +struct _InitialValue end + +struct BottomRF{T} + rf::T end +@inline (op::BottomRF)(::_InitialValue, x) = x +@inline (op::BottomRF)(acc, x) = op.rf(acc, x) + +struct MappingRF{F, T} + f::F + rf::T +end + +@inline (op::MappingRF)(acc, x) = op.rf(acc, op.f(x)) + +struct FilteringRF{F, T} + f::F + rf::T +end + +@inline (op::FilteringRF)(acc, x) = op.f(x) ? op.rf(acc, x) : acc + +""" + _xfadjoint(op, itr) -> op′, itr′ + +Given a pair of reducing function `op` and an iterator `itr`, return a pair +`(op′, itr′)` of similar types. If the iterator `itr` is transformed by an +iterator transform `ixf` whose adjoint transducer `xf` is known, `op′ = xf(op)` +and `itr′ = "parent" of itr` is returned. Otherwise, `op` and `itr` are +returned as-is. For example, transducer `rf -> MappingRF(f, rf)` is the +adjoint of iterator transform `itr -> Generator(f, itr)`. + +Nested iterator transforms are converted recursively. That is to say, +given `op` and + + itr = (ixf₁ ∘ ixf₂ ∘ ... ∘ ixfₙ)(itr′) + +what is returned is `itr′` and + + op′ = (xfₙ ∘ ... ∘ xf₂ ∘ xf₁)(op) +""" +_xfadjoint(op, itr) = (op, itr) +_xfadjoint(op, itr::Generator) = + if itr.f === identity + op, itr.iter + else + _xfadjoint(MappingRF(itr.f, op), itr.iter) + end +_xfadjoint(op, itr::Filter) = + _xfadjoint(FilteringRF(itr.flt, op), itr.itr) """ mapfoldl(f, op, itr; [init]) @@ -92,21 +141,14 @@ foldl(op, itr; kw...) = mapfoldl(identity, op, itr; kw...) ## foldr & mapfoldr mapfoldr_impl(f, op, nt::NamedTuple{(:init,)}, itr) = - mapfoldl_impl(f, (x,y) -> op(y,x), nt, Iterators.reverse(itr)) - -# we can't just call mapfoldl_impl with (x,y) -> op(y,x), because -# we need to use the type of op for mapreduce_empty_iter and mapreduce_first. -function mapfoldr_impl(f, op, nt::NamedTuple{()}, itr) - ritr = Iterators.reverse(itr) - y = iterate(ritr) - if y === nothing - return Base.mapreduce_empty_iter(f, op, itr, IteratorEltype(itr)) - end - x, i = y - init = mapreduce_first(f, op, x) - return mapfoldl_impl(f, (x,y) -> op(y,x), (init=init,), ritr, i) + mapfoldl_impl(f, FlipArgs(op), nt, Iterators.reverse(itr)) + +struct FlipArgs{F} + f::F end +@inline (f::FlipArgs)(x, y) = f.f(y, x) + """ mapfoldr(f, op, itr; [init]) @@ -234,6 +276,11 @@ reduce_empty(::typeof(mul_prod), T) = reduce_empty(*, T) reduce_empty(::typeof(mul_prod), ::Type{T}) where {T<:SmallSigned} = one(Int) reduce_empty(::typeof(mul_prod), ::Type{T}) where {T<:SmallUnsigned} = one(UInt) +reduce_empty(op::BottomRF, T) = reduce_empty(op.rf, T) +reduce_empty(op::MappingRF, T) = mapreduce_empty(op.f, op.rf, T) +reduce_empty(op::FilteringRF, T) = reduce_empty(op.rf, T) +reduce_empty(op::FlipArgs, T) = reduce_empty(op.f, T) + """ Base.mapreduce_empty(f, op, T) @@ -251,10 +298,12 @@ mapreduce_empty(::typeof(abs2), op, T) = abs2(reduce_empty(op, T)) mapreduce_empty(f::typeof(abs), ::typeof(max), T) = abs(zero(T)) mapreduce_empty(f::typeof(abs2), ::typeof(max), T) = abs2(zero(T)) -mapreduce_empty_iter(f, op, itr, ::HasEltype) = mapreduce_empty(f, op, eltype(itr)) -mapreduce_empty_iter(f, op::typeof(&), itr, ::EltypeUnknown) = true -mapreduce_empty_iter(f, op::typeof(|), itr, ::EltypeUnknown) = false -mapreduce_empty_iter(f, op, itr, ::EltypeUnknown) = _empty_reduce_error() +# For backward compatibility: +mapreduce_empty_iter(f, op, itr, ItrEltype) = + reduce_empty_iter(MappingRF(f, op), itr, ItrEltype) + +reduce_empty_iter(op, itr, ::HasEltype) = reduce_empty(op, eltype(itr)) +reduce_empty_iter(op, itr, ::EltypeUnknown) = _empty_reduce_error() # handling of single-element iterators """ From 2ac3fc4c5ac8358558a9d1dc8c2bbbe40ae97b8e Mon Sep 17 00:00:00 2001 From: Takafumi Arakaki Date: Thu, 10 Oct 2019 20:30:50 -0700 Subject: [PATCH 02/10] Fix mapfoldr --- base/reduce.jl | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/base/reduce.jl b/base/reduce.jl index d4b7402fa7d45..535086fdc2a13 100644 --- a/base/reduce.jl +++ b/base/reduce.jl @@ -140,8 +140,10 @@ foldl(op, itr; kw...) = mapfoldl(identity, op, itr; kw...) ## foldr & mapfoldr -mapfoldr_impl(f, op, nt::NamedTuple{(:init,)}, itr) = - mapfoldl_impl(f, FlipArgs(op), nt, Iterators.reverse(itr)) +function mapfoldr_impl(f, op, nt, itr) + op′, itr′ = _xfadjoint(BottomRF(FlipArgs(op)), Generator(f, itr)) + return _foldl_impl(op′, nt, Iterators.reverse(itr′)) +end struct FlipArgs{F} f::F From a2e00294d16b131dec3cd3afb84f914f1819535c Mon Sep 17 00:00:00 2001 From: Takafumi Arakaki Date: Thu, 10 Oct 2019 20:40:02 -0700 Subject: [PATCH 03/10] Fix empty value handling --- base/reduce.jl | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/base/reduce.jl b/base/reduce.jl index 535086fdc2a13..f34b9e33d69c7 100644 --- a/base/reduce.jl +++ b/base/reduce.jl @@ -48,14 +48,17 @@ function _foldl_impl(op, nt, itr) # Unroll the while loop once; if init is known, the call to op may # be evaluated at compile time y = iterate(itr) - y === nothing && return init + if y === nothing + init isa _InitialValue && return reduce_empty_iter(op, itr) + return init + end v = op(init, y[1]) while true y = iterate(itr, y[2]) y === nothing && break v = op(v, y[1]) end - v isa _InitialValue && reduce_empty_iter(op, itr, IteratorEltype(itr)) + v isa _InitialValue && return reduce_empty_iter(op, itr) return v end @@ -304,6 +307,7 @@ mapreduce_empty(f::typeof(abs2), ::typeof(max), T) = abs2(zero(T)) mapreduce_empty_iter(f, op, itr, ItrEltype) = reduce_empty_iter(MappingRF(f, op), itr, ItrEltype) +reduce_empty_iter(op, itr) = reduce_empty_iter(op, itr, IteratorEltype(itr)) reduce_empty_iter(op, itr, ::HasEltype) = reduce_empty(op, eltype(itr)) reduce_empty_iter(op, itr, ::EltypeUnknown) = _empty_reduce_error() From abbbb620da3e814ba9bc8eee909adaf4bbc7b7b3 Mon Sep 17 00:00:00 2001 From: Takafumi Arakaki Date: Thu, 10 Oct 2019 21:41:26 -0700 Subject: [PATCH 04/10] Specialize _foldl_impl for Tuple --- base/tuple.jl | 15 ++++----------- 1 file changed, 4 insertions(+), 11 deletions(-) diff --git a/base/tuple.jl b/base/tuple.jl index d54728a5d4bc5..86c14fd952ac8 100644 --- a/base/tuple.jl +++ b/base/tuple.jl @@ -204,18 +204,11 @@ function map(f, t1::Any16, t2::Any16, ts::Any16...) (A...,) end -# mapafoldl, based on afold in operators.jl -mapafoldl(F,op,a) = a -mapafoldl(F,op,a,b) = op(a,F(b)) -mapafoldl(F,op,a,b,c...) = mapafoldl(F, op, op(a,F(b)), c...) -function mapafoldl(F,op,a,b,c,d,e,f,g,h,i,j,k,l,m,n,o,p,qs...) - y = op(op(op(op(op(op(op(op(op(op(op(op(op(op(op(a,F(b)),F(c)),F(d)),F(e)),F(f)),F(g)),F(h)),F(i)),F(j)),F(k)),F(l)),F(m)),F(n)),F(o)),F(p)) - for x in qs; y = op(y,F(x)); end - y +function _foldl_impl(op, nt, itr::Tuple) + init = get(nt, :init, _InitialValue()) + y = afoldl(op, init, itr...) + return y isa _InitialValue ? reduce_empty_iter(op, itr) : y end -mapfoldl_impl(f, op, nt::NamedTuple{(:init,)}, t::Tuple) = mapafoldl(f, op, nt.init, t...) -mapfoldl_impl(f, op, nt::NamedTuple{()}, t::Tuple) = mapafoldl(f, op, f(t[1]), tail(t)...) -mapfoldl_impl(f, op, nt::NamedTuple{()}, t::Tuple{}) = mapreduce_empty_iter(f, op, t, IteratorEltype(t)) # type-stable padding fill_to_length(t::NTuple{N,Any}, val, ::Val{N}) where {N} = t From 6c0c0c1c88241a7bbaae59cc5b01b198793e8d2d Mon Sep 17 00:00:00 2001 From: Takafumi Arakaki Date: Thu, 10 Oct 2019 21:54:43 -0700 Subject: [PATCH 05/10] =?UTF-8?q?Fix=20@=EF=BB=BFallocated(foldr(-,=20x))?= =?UTF-8?q?=20test?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- base/reduce.jl | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/base/reduce.jl b/base/reduce.jl index f34b9e33d69c7..c94d1b154cc20 100644 --- a/base/reduce.jl +++ b/base/reduce.jl @@ -307,8 +307,8 @@ mapreduce_empty(f::typeof(abs2), ::typeof(max), T) = abs2(zero(T)) mapreduce_empty_iter(f, op, itr, ItrEltype) = reduce_empty_iter(MappingRF(f, op), itr, ItrEltype) -reduce_empty_iter(op, itr) = reduce_empty_iter(op, itr, IteratorEltype(itr)) -reduce_empty_iter(op, itr, ::HasEltype) = reduce_empty(op, eltype(itr)) +@inline reduce_empty_iter(op, itr) = reduce_empty_iter(op, itr, IteratorEltype(itr)) +@inline reduce_empty_iter(op, itr, ::HasEltype) = reduce_empty(op, eltype(itr)) reduce_empty_iter(op, itr, ::EltypeUnknown) = _empty_reduce_error() # handling of single-element iterators From d70605606b0d58fbf1bc1f9abe7c1d81e164e022 Mon Sep 17 00:00:00 2001 From: Takafumi Arakaki Date: Thu, 10 Oct 2019 23:10:23 -0700 Subject: [PATCH 06/10] Use reduce_first in foldl --- base/reduce.jl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/base/reduce.jl b/base/reduce.jl index c94d1b154cc20..33672216655af 100644 --- a/base/reduce.jl +++ b/base/reduce.jl @@ -68,7 +68,7 @@ struct BottomRF{T} rf::T end -@inline (op::BottomRF)(::_InitialValue, x) = x +@inline (op::BottomRF)(::_InitialValue, x) = reduce_first(op.rf, x) @inline (op::BottomRF)(acc, x) = op.rf(acc, x) struct MappingRF{F, T} From 3a266b3e9e0ca51eb22e8ebfc0848290bbd54ef0 Mon Sep 17 00:00:00 2001 From: Takafumi Arakaki Date: Thu, 10 Oct 2019 23:24:54 -0700 Subject: [PATCH 07/10] Add/tweak docstrings --- base/reduce.jl | 23 ++++++++++++++++++++--- 1 file changed, 20 insertions(+), 3 deletions(-) diff --git a/base/reduce.jl b/base/reduce.jl index 33672216655af..7b9694a669fd5 100644 --- a/base/reduce.jl +++ b/base/reduce.jl @@ -64,6 +64,13 @@ end struct _InitialValue end +""" + BottomRF(rf) -> rf′ + +"Bottom" reducing function. This is a thin wrapper around the `op` argument +passed to `foldl`-like functions for handling the initial invocation to call +[`reduce_first`](@ref). +""" struct BottomRF{T} rf::T end @@ -71,6 +78,11 @@ end @inline (op::BottomRF)(::_InitialValue, x) = reduce_first(op.rf, x) @inline (op::BottomRF)(acc, x) = op.rf(acc, x) +""" + MappingRF(f, rf) -> rf′ + +Create a mapping reducing function `rf′(acc, x) = rf(acc, f(x))`. +""" struct MappingRF{F, T} f::F rf::T @@ -78,6 +90,11 @@ end @inline (op::MappingRF)(acc, x) = op.rf(acc, op.f(x)) +""" + FilteringRF(f, rf) -> rf′ + +Create a filtering reducing function `rf′(acc, x) = f(x) ? rf(acc, x) : acc`. +""" struct FilteringRF{F, T} f::F rf::T @@ -91,9 +108,9 @@ end Given a pair of reducing function `op` and an iterator `itr`, return a pair `(op′, itr′)` of similar types. If the iterator `itr` is transformed by an iterator transform `ixf` whose adjoint transducer `xf` is known, `op′ = xf(op)` -and `itr′ = "parent" of itr` is returned. Otherwise, `op` and `itr` are -returned as-is. For example, transducer `rf -> MappingRF(f, rf)` is the -adjoint of iterator transform `itr -> Generator(f, itr)`. +and `itr′ = ixf⁻¹(itr)` is returned. Otherwise, `op` and `itr` are returned +as-is. For example, transducer `rf -> MappingRF(f, rf)` is the adjoint of +iterator transform `itr -> Generator(f, itr)`. Nested iterator transforms are converted recursively. That is to say, given `op` and From dfcd79287d300648c1fecf8ed798e0a6ad754cb7 Mon Sep 17 00:00:00 2001 From: Takafumi Arakaki Date: Sat, 12 Oct 2019 09:55:04 -0700 Subject: [PATCH 08/10] Add a transducer for Flatten --- base/reduce.jl | 35 ++++++++++++++++++++++++----------- base/tuple.jl | 6 +----- 2 files changed, 25 insertions(+), 16 deletions(-) diff --git a/base/reduce.jl b/base/reduce.jl index 7b9694a669fd5..4cd8bfcf144c3 100644 --- a/base/reduce.jl +++ b/base/reduce.jl @@ -36,29 +36,28 @@ mul_prod(x::Real, y::Real)::Real = x * y ## foldl && mapfoldl -mapfoldl_impl(f, op, nt, itr) = foldl_impl(op, nt, Generator(f, itr)) +function mapfoldl_impl(f, op, nt, itr) + op′, itr′ = _xfadjoint(BottomRF(op), Generator(f, itr)) + return foldl_impl(op′, nt, itr′) +end function foldl_impl(op, nt, itr) - op′, itr′ = _xfadjoint(BottomRF(op), itr) - return _foldl_impl(op′, nt, itr′) + v = _foldl_impl(op, get(nt, :init, _InitialValue()), itr) + v isa _InitialValue && return reduce_empty_iter(op, itr) + return v end -function _foldl_impl(op, nt, itr) - init = get(nt, :init, _InitialValue()) +function _foldl_impl(op, init, itr) # Unroll the while loop once; if init is known, the call to op may # be evaluated at compile time y = iterate(itr) - if y === nothing - init isa _InitialValue && return reduce_empty_iter(op, itr) - return init - end + y === nothing && return init v = op(init, y[1]) while true y = iterate(itr, y[2]) y === nothing && break v = op(v, y[1]) end - v isa _InitialValue && return reduce_empty_iter(op, itr) return v end @@ -102,6 +101,18 @@ end @inline (op::FilteringRF)(acc, x) = op.f(x) ? op.rf(acc, x) : acc +""" + FlatteningRF(rf) -> rf′ + +Create a flattening reducing function that is roughly equivalent to +`rf′(acc, x) = foldl(rf, x; init=acc)`. +""" +struct FlatteningRF{T} + rf::T +end + +@inline (op::FlatteningRF)(acc, x) = _foldl_impl(op.rf, acc, x) + """ _xfadjoint(op, itr) -> op′, itr′ @@ -130,6 +141,8 @@ _xfadjoint(op, itr::Generator) = end _xfadjoint(op, itr::Filter) = _xfadjoint(FilteringRF(itr.flt, op), itr.itr) +_xfadjoint(op, itr::Flatten) = + _xfadjoint(FlatteningRF(op), itr.it) """ mapfoldl(f, op, itr; [init]) @@ -162,7 +175,7 @@ foldl(op, itr; kw...) = mapfoldl(identity, op, itr; kw...) function mapfoldr_impl(f, op, nt, itr) op′, itr′ = _xfadjoint(BottomRF(FlipArgs(op)), Generator(f, itr)) - return _foldl_impl(op′, nt, Iterators.reverse(itr′)) + return foldl_impl(op′, nt, Iterators.reverse(itr′)) end struct FlipArgs{F} diff --git a/base/tuple.jl b/base/tuple.jl index 86c14fd952ac8..7881d57323845 100644 --- a/base/tuple.jl +++ b/base/tuple.jl @@ -204,11 +204,7 @@ function map(f, t1::Any16, t2::Any16, ts::Any16...) (A...,) end -function _foldl_impl(op, nt, itr::Tuple) - init = get(nt, :init, _InitialValue()) - y = afoldl(op, init, itr...) - return y isa _InitialValue ? reduce_empty_iter(op, itr) : y -end +_foldl_impl(op, init, itr::Tuple) = afoldl(op, init, itr...) # type-stable padding fill_to_length(t::NTuple{N,Any}, val, ::Val{N}) where {N} = t From af0b06ce5713afbdb70700c8b40150ab54c5c2fb Mon Sep 17 00:00:00 2001 From: Takafumi Arakaki Date: Tue, 3 Dec 2019 18:43:12 -0800 Subject: [PATCH 09/10] Improve _xfadjoint(op, itr::Generator); process inner iterator --- base/reduce.jl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/base/reduce.jl b/base/reduce.jl index eae5da9820b8c..a969ea7a06102 100644 --- a/base/reduce.jl +++ b/base/reduce.jl @@ -135,7 +135,7 @@ what is returned is `itr′` and _xfadjoint(op, itr) = (op, itr) _xfadjoint(op, itr::Generator) = if itr.f === identity - op, itr.iter + _xfadjoint(op, itr.iter) else _xfadjoint(MappingRF(itr.f, op), itr.iter) end From ce49c7ea5bf721f825410d2bc5e35d4c51f016cf Mon Sep 17 00:00:00 2001 From: Takafumi Arakaki Date: Tue, 3 Dec 2019 18:44:23 -0800 Subject: [PATCH 10/10] Take adjoint inside FlatteningRF as well --- base/reduce.jl | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/base/reduce.jl b/base/reduce.jl index a969ea7a06102..2c97effdf9550 100644 --- a/base/reduce.jl +++ b/base/reduce.jl @@ -111,7 +111,10 @@ struct FlatteningRF{T} rf::T end -@inline (op::FlatteningRF)(acc, x) = _foldl_impl(op.rf, acc, x) +@inline function (op::FlatteningRF)(acc, x) + op′, itr′ = _xfadjoint(BottomRF(op.rf), x) + return _foldl_impl(op′, acc, itr′) +end """ _xfadjoint(op, itr) -> op′, itr′