More idiomatic way of phrasing `reduce(\circ, repeated(f, x))(y)`?

I usually use reduce(∘, repeated(func, x))(a) when I need to apply func to a x times, but apparently it spends most of the time creating the single composed function; averaging ~20x as long as an equivalent accumulate in this example despite not needing any allocs/gc:

julia> f(a) = reduce(∘, repeated(exp, 1000))(a)
f (generic function with 1 method)

julia> @benchmark f(10.)
BenchmarkTools.Trial: 10000 samples with 1 evaluation per sample.
 Range (min … max):  132.725 μs … 844.193 μs  ┊ GC (min … max): 0.00% … 0.00%
 Time  (median):     171.776 μs               ┊ GC (median):    0.00%
 Time  (mean ± σ):   173.105 μs ±  40.335 μs  ┊ GC (mean ± σ):  0.00% ± 0.00%

      █▄      ▆
  ▂▂▁▂██▄▃▃▂▂▂█▆▅▄▂▂▂▂▂▂▂▂▁▁▁▂▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁ ▂
  133 μs           Histogram: frequency by time          331 μs <

 Memory estimate: 32 bytes, allocs estimate: 2.

julia> f(a) = accumulate((x,_)->exp(x), repeated(nothing, 1000); init=a)[end]
f (generic function with 1 method)

julia> @benchmark f(10.)
BenchmarkTools.Trial: 10000 samples with 4 evaluations per sample.
 Range (min … max):  6.148 μs …  5.001 ms  ┊ GC (min … max): 0.00% … 90.01%
 Time  (median):     7.973 μs              ┊ GC (median):    0.00%
 Time  (mean ± σ):   8.881 μs ± 49.997 μs  ┊ GC (mean ± σ):  5.07% ±  0.90%

  ▂    ▇▅▄▄▃▆█▆▆▄█▆▄▃▂▂▂▁▂▁▁▁▂▁▁                             ▂
  █▇▇▇▄███████████████████████████████▇██▇▇▇▇▆▅▆▆▆▆▆▆▅▆▅▆▄▄▄ █
  6.15 μs      Histogram: log(frequency) by time     14.9 μs <

 Memory estimate: 7.85 KiB, allocs estimate: 2.

is there a way to tell julia to just apply the functions directly instead of first collecting them into a composed function?

of course the magic related section is better at searching than the search bar or the related posts that pop up when composing a thread.

from here (mentioned here)

julia> f(a) = foldl((x,_) -> exp(x), repeated(nothing, 1000); init=a)
f (generic function with 1 method)

julia> @benchmark f(10.)
BenchmarkTools.Trial: 10000 samples with 999 evaluations per sample.
 Range (min … max):  8.146 ns … 25.388 ns  ┊ GC (min … max): 0.00% … 0.00%
 Time  (median):     8.871 ns              ┊ GC (median):    0.00%
 Time  (mean ± σ):   8.995 ns ±  1.472 ns  ┊ GC (mean ± σ):  0.00% ± 0.00%

  █▄   ▅█▂ ▃                                                 ▁
  ██▇▆▇█████▇▇▆▆▆▆▆▆▆▆▆▆▅▆▆▆▅▆▆▅▆▇▅▇▇▆▆▇▆▇▇▇▅▆▇▆▇▆▇▇▇▆▇▇▇▇▆▆ █
  8.15 ns      Histogram: log(frequency) by time     15.2 ns <

 Memory estimate: 0 bytes, allocs estimate: 0.

Yeah, I was going to suggest the foldl approach, but you might also be interested in the iterated iterator from IterTools.jl.

julia> using IterTools

help?> iterated
search: iterated iterate @generated Iterators issorted insorted inertia interleaveby ismarked

  iterated(f, x)

  Iterate over successive applications of f, as in x, f(x), f(f(x)), f(f(f(x))), ...

  Use Base.Iterators.take() to obtain the required number of elements.

  julia> for i in Iterators.take(iterated(x -> 2x, 1), 5)
             @show i
         end
  i = 1
  i = 2
  i = 4
  i = 8
  i = 16
  
  julia> for i in Iterators.take(iterated(sqrt, 100), 6)
             @show i
         end
  i = 100
  i = 10.0
  i = 3.1622776601683795
  i = 1.7782794100389228
  i = 1.333521432163324
  i = 1.1547819846894583

This thing probably isn’t as performant as your foldl loop though.

julia> using FunctionChains: ∘̂

julia> 1 |> exp ∘̂ 3
3.814279104760214e6

looks like it’s about as fast as accumulate but without allocations. not as impressive as the foldl method, but uses less unnecessary letters:

julia> f(a) = a |> exp ∘̂ 1000
f (generic function with 1 method)

julia> @benchmark f(10.)
BenchmarkTools.Trial: 10000 samples with 5 evaluations per sample.
 Range (min … max):  6.468 μs …  18.651 μs  ┊ GC (min … max): 0.00% … 0.00%
 Time  (median):     6.532 μs               ┊ GC (median):    0.00%
 Time  (mean ± σ):   6.661 μs ± 637.748 ns  ┊ GC (mean ± σ):  0.00% ± 0.00%

  ██▂                                                         ▁
  ████▇█▇▇▆▆▆▆▇█████▆▇▆▆▆▆▆▅▅▆▅▆▅▅▆▆▅▆▅▅▆▅▅▅▃▅▅▃▅▅▅▆▆▃▅▃▄▅▃▅▄ █
  6.47 μs      Histogram: log(frequency) by time      9.53 μs <

 Memory estimate: 0 bytes, allocs estimate: 0.

looks like a just a straight up better version of reduce(∘, repeated(f, x)), since it exposes a handle to the composed function as well. maybe the function in base could be optimized better?

1000 applications of exp in 8ns, does that seem reasonable to you? I would expect one single exp call to take close to that long.

huh, yeah. that is weird. think I was on master instead of release; going back to release goes back to μs range

julia> f(a) = foldl((x,_) -> exp(x), repeated(nothing, 1000); init=a)
f (generic function with 1 method)

julia> @benchmark f(10.)
BenchmarkTools.Trial: 10000 samples with 5 evaluations per sample.
 Range (min … max):  5.775 μs … 60.120 μs  ┊ GC (min … max): 0.00% … 0.00%
 Time  (median):     6.536 μs              ┊ GC (median):    0.00%
 Time  (mean ± σ):   7.309 μs ±  1.955 μs  ┊ GC (mean ± σ):  0.00% ± 0.00%

  ▁     █▃▁▁▁  ▁▁   ▄▄            ▄                      ▃   ▁
  █▅▁▃▁▃█████▇▇██▇▇▇█████████▇▇▇▇▇█▇▇▆▆▇▆▇▇▆▆▅▆▅▅▄▅▄▅▅▄▅▅█▅▅ █
  5.78 μs      Histogram: log(frequency) by time     12.3 μs <

 Memory estimate: 0 bytes, allocs estimate: 0.

maybe master decided to start memorizing? the majority of calls will be exp(Inf) = Inf, and one exp() is only about 2 ns, so it’s possible that it realized we weren’t using the second argument and just returned the repeated value. using 10 instead of 1000 takes about the same time on master

it was compilation:

julia> @benchmark f($10.)
BenchmarkTools.Trial: 10000 samples with 5 evaluations per sample.
 Range (min … max):  5.783 μs … 48.659 μs  ┊ GC (min … max): 0.00% … 0.00%
 Time  (median):     6.544 μs              ┊ GC (median):    0.00%
 Time  (mean ± σ):   6.853 μs ±  1.082 μs  ┊ GC (mean ± σ):  0.00% ± 0.00%

         ▇█▂▁                                                ▁
  ▅▁▁▁▃▁▁█████████▇▇█▇▇███████▇█▇▇███████▇███▇▇▇▆▆▆▅▆▆▆▅▅▆▄▄ █
  5.78 μs      Histogram: log(frequency) by time     11.1 μs <

 Memory estimate: 0 bytes, allocs estimate: 0.

julia> @benchmark f(10.)
BenchmarkTools.Trial: 10000 samples with 999 evaluations per sample.
 Range (min … max):  8.876 ns … 22.079 ns  ┊ GC (min … max): 0.00% … 0.00%
 Time  (median):     8.968 ns              ┊ GC (median):    0.00%
 Time  (mean ± σ):   9.595 ns ±  1.171 ns  ┊ GC (mean ± σ):  0.00% ± 0.00%

  █▆    ▁▁     ▂▂      ▆▅                                    ▂
  ███▅▆▁██▄▅▅▅▆██▅▄▅▅▅▇███▆▅▅▅▅▆█▆▄▅▅▆▅▇▅▅▅▅▆▆▅▆▅▅▅▅▅▅▄▅▃▁▅▅ █
  8.88 ns      Histogram: log(frequency) by time     14.2 ns <

 Memory estimate: 0 bytes, allocs estimate: 0.

(as stated here):

function _foldl_impl(op::OP, init, itr) where {OP}
    # 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])
    # 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

Here are my results of the four method discussed in this thread:

using BenchmarkTools, Base.Iterators, IterTools

f1(a) = reduce(∘, repeated(exp, 1000))(a)
f2(a) = accumulate((x, _) -> exp(x), repeated(nothing, 1000); init=a)[end]
f3(a) = foldl((x, _) -> exp(x), repeated(nothing, 1000); init=a)
f4(a) = first(Iterators.drop(iterated(exp, a), 1000))

# Warmup + assert equal
@assert f1(10.0) == f2(10.0) == f3(10.0) == f4(10.0)

show(stdout, MIME("text/plain"), @benchmark f1(10.0)); println()
#> BenchmarkTools.Trial: 10000 samples with 1 evaluation per sample.
#>  Range (min … max):  55.516 μs … 555.481 μs  ┊ GC (min … max): 0.00% … 0.00%
#>  Time  (median):     58.083 μs               ┊ GC (median):    0.00%
#>  Time  (mean ± σ):   59.279 μs ±  14.249 μs  ┊ GC (mean ± σ):  0.00% ± 0.00%
#> 
#>       █▇▄▃▁  ▁                                                  
#>   ▁▁▂▇█████▇▇██▆▆▆▄▃▄▄▂▂▂▁▁▁▂▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁ ▂
#>   55.5 μs         Histogram: frequency by time           73 μs <
#> 
#>  Memory estimate: 32 bytes, allocs estimate: 2.
show(stdout, MIME("text/plain"), @benchmark f2(10.0)); println()
#> BenchmarkTools.Trial: 10000 samples with 9 evaluations per sample.
#>  Range (min … max):  2.139 μs … 32.680 μs  ┊ GC (min … max): 0.00% … 78.02%
#>  Time  (median):     2.208 μs              ┊ GC (median):    0.00%
#>  Time  (mean ± σ):   2.377 μs ±  1.676 μs  ┊ GC (mean ± σ):  5.19% ±  6.65%
#> 
#>   ▅██▆▃          ▁▁                                          ▂
#>   █████▇▆▇▆▇▆▆▆▆████▇▆▆▆▆▆▅▅▃▄▁▃▁▁▃▃▃▁▁▄▁▄▁▃▃▁▃▄▁▁▁▁▁▁▁▃▁▁▁▃ █
#>   2.14 μs      Histogram: log(frequency) by time     4.06 μs <
#> 
#>  Memory estimate: 7.88 KiB, allocs estimate: 3.
show(stdout, MIME("text/plain"), @benchmark f3(10.0)); println()
#> BenchmarkTools.Trial: 10000 samples with 9 evaluations per sample.
#>  Range (min … max):  2.071 μs …  21.754 μs  ┊ GC (min … max): 0.00% … 0.00%
#>  Time  (median):     2.073 μs               ┊ GC (median):    0.00%
#>  Time  (mean ± σ):   2.122 μs ± 534.683 ns  ┊ GC (mean ± σ):  0.00% ± 0.00%
#> 
#>   █ ▃         ▃▂▄ ▄ ▄ ▂                                       ▁
#>   ███▅▃▁▁▃▁▁▁▆█████▄█▃█▃█▁▃▃▄▃▄▁▃▁▁▁▁▁▁▁▄▁▁▃▁▁▅▃▆▆▆▆▆▅▅▃▄▄▅▆▆ █
#>   2.07 μs      Histogram: log(frequency) by time      2.43 μs <
#> 
#>  Memory estimate: 0 bytes, allocs estimate: 0.
show(stdout, MIME("text/plain"), @benchmark f4(10.0)); println()
#> BenchmarkTools.Trial: 10000 samples with 9 evaluations per sample.
#>  Range (min … max):  2.110 μs …   4.229 μs  ┊ GC (min … max): 0.00% … 0.00%
#>  Time  (median):     2.123 μs               ┊ GC (median):    0.00%
#>  Time  (mean ± σ):   2.146 μs ± 164.411 ns  ┊ GC (mean ± σ):  0.00% ± 0.00%
#> 
#>    █                                                           
#>   ▆█▃▄▂▁▁▂▂▂▂▂▁▁▁▁▁▂▁▁▁▁▂▂▂▁▁▂▂▂▂▂▂▂▂▂▂▂▂▂▂▂▂▂▂▂▂▂▂▂▂▂▂▂▂▂▂▂▂ ▂
#>   2.11 μs         Histogram: frequency by time        2.59 μs <
#> 
#>  Memory estimate: 0 bytes, allocs estimate: 0.

Created on 2026-08-07 with MinimalWorkingExamples v1.0.0 using Julia 1.12.6

Environment
Julia Version 1.12.6
Commit 15346901f00 (2026-04-09 19:20 UTC)
Build Info:
  Official https://julialang.org release
Platform Info:
  OS: Linux (x86_64-linux-gnu)
  CPU: 16 × AMD Ryzen 7 8845HS w/ Radeon 780M Graphics
  WORD_SIZE: 64
  LLVM: libLLVM-18.1.7 (ORCJIT, znver3)
  GC: Built with stock GC
Threads: 1 default, 1 interactive, 1 GC (on 16 virtual cores)
Environment:
  JULIA_LOAD_PATH = @:@stdlib

here’s master with those same functions:

julia> show(stdout, MIME("text/plain"), @benchmark f1($10.0)); println()
BenchmarkTools.Trial: 10000 samples with 1 evaluation per sample.
 Range (min … max):  154.901 μs …  1.272 ms  ┊ GC (min … max): 0.00% … 0.00%
 Time  (median):     166.506 μs              ┊ GC (median):    0.00%
 Time  (mean ± σ):   174.343 μs ± 26.890 μs  ┊ GC (mean ± σ):  0.00% ± 0.00%

      ▃█▇▄▂▃▃▃▃▃▄▃▃▂▂▁▁▁▁                                      ▁
  ▅▅▂▄██████████████████████▇▇▇▆▆▇▆▆▆▆▅▆▆▆▄▆▅▅▅▂▅▃▄▅▅▅▂▂▄▄▃▄▂▅ █
  155 μs        Histogram: log(frequency) by time       266 μs <

 Memory estimate: 32 bytes, allocs estimate: 2.

julia> show(stdout, MIME("text/plain"), @benchmark f2($10.0)); println()
BenchmarkTools.Trial: 10000 samples with 4 evaluations per sample.
 Range (min … max):  6.750 μs … 422.931 μs  ┊ GC (min … max): 0.00% … 94.64%
 Time  (median):     7.289 μs               ┊ GC (median):    0.00%
 Time  (mean ± σ):   8.438 μs ±  16.250 μs  ┊ GC (mean ± σ):  7.69% ±  3.94%

    ▇██▇▅▄▃▂▂▁▁▁▁▁                                            ▂
  ▄▂███████████████████████████▇████▇▇█▆█▇▆▇▆▅▆▆▆▄▆▅▄▅▅▄▃▄▄▄▅ █
  6.75 μs      Histogram: log(frequency) by time      13.9 μs <

 Memory estimate: 7.88 KiB, allocs estimate: 3.

julia> show(stdout, MIME("text/plain"), @benchmark f3($10.0)); println()
BenchmarkTools.Trial: 10000 samples with 5 evaluations per sample.
 Range (min … max):  5.783 μs … 62.308 μs  ┊ GC (min … max): 0.00% … 0.00%
 Time  (median):     6.863 μs              ┊ GC (median):    0.00%
 Time  (mean ± σ):   7.457 μs ±  1.904 μs  ┊ GC (mean ± σ):  0.00% ± 0.00%

  ▁    █▇▃▁▂  ▂█▁  ▄▁▁▁▁▁  ▁  ▁ ▁                    ▄▁      ▂
  █▅▁▅▄██████▇████▇█████████████████▆█▇▇▇▇▆▆▆▇▆▅▆▆▅▆▅██▆▆▆▆▆ █
  5.78 μs      Histogram: log(frequency) by time     12.8 μs <

 Memory estimate: 0 bytes, allocs estimate: 0.

julia> show(stdout, MIME("text/plain"), @benchmark f4($10.0)); println()
BenchmarkTools.Trial: 10000 samples with 5 evaluations per sample.
 Range (min … max):  5.784 μs …  29.078 μs  ┊ GC (min … max): 0.00% … 0.00%
 Time  (median):     6.548 μs               ┊ GC (median):    0.00%
 Time  (mean ± σ):   6.780 μs ± 913.185 ns  ┊ GC (mean ± σ):  0.00% ± 0.00%

  ▁       ██▂▁▁    ▁▁                                         ▂
  █▄▁▁▄▃▁▃█████████████▇█▇███▇▇▇▇▇▇▇▇▇▇▇▇▇█▆▇▇█▇▇▆▇▇▆▇▇▅▆▆▅▄▆ █
  5.78 μs      Histogram: log(frequency) by time      10.7 μs <

 Memory estimate: 0 bytes, allocs estimate: 0.

julia> #now let them compile everything away by removing the $

julia> show(stdout, MIME("text/plain"), @benchmark f1(10.0)); println()
BenchmarkTools.Trial: 10000 samples with 1 evaluation per sample.
 Range (min … max):  162.456 μs … 586.251 μs  ┊ GC (min … max): 0.00% … 0.00%
 Time  (median):     166.788 μs               ┊ GC (median):    0.00%
 Time  (mean ± σ):   178.351 μs ±  25.721 μs  ┊ GC (mean ± σ):  0.00% ± 0.00%

  ▄█▇▄▄▄▃▃▃▂▂▂▃▃▁▁▁▁▁▁  ▂▃▁  ▁                ▁▂▁               ▂
  ██████████████████████████████▇▇▇▇▆▇▆▇▆▇▆▆▅▇█████▇▇▇▆▅▆▅▅▅▂▄▄ █
  162 μs        Histogram: log(frequency) by time        263 μs <

 Memory estimate: 32 bytes, allocs estimate: 2.

julia> show(stdout, MIME("text/plain"), @benchmark f2(10.0)); println()
BenchmarkTools.Trial: 10000 samples with 5 evaluations per sample.
 Range (min … max):  6.937 μs … 776.736 μs  ┊ GC (min … max): 0.00% … 87.47%
 Time  (median):     7.812 μs               ┊ GC (median):    0.00%
 Time  (mean ± σ):   9.227 μs ±  19.372 μs  ┊ GC (mean ± σ):  8.45% ±  4.15%

  ▇█▇▅▄▅▆▆▅▄▄▃▂▂▂▂▂▁▁▁▁▁ ▁   ▁▁        ▁▁▂▂▁                  ▂
  ███████████████████████████████▇▇▇▆▆▆██████▇▆▇▇▆▇▆▆▆▆▆▅▅▅▄▅ █
  6.94 μs      Histogram: log(frequency) by time      16.2 μs <

 Memory estimate: 7.85 KiB, allocs estimate: 2.

julia> show(stdout, MIME("text/plain"), @benchmark f3(10.0)); println()
BenchmarkTools.Trial: 10000 samples with 999 evaluations per sample.
 Range (min … max):  9.012 ns … 128.866 ns  ┊ GC (min … max): 0.00% … 0.00%
 Time  (median):     9.093 ns               ┊ GC (median):    0.00%
 Time  (mean ± σ):   9.759 ns ±   2.875 ns  ┊ GC (mean ± σ):  0.00% ± 0.00%

  █▅▂▁ ▃    ▃▁    ▁▂     ▂▅▃                                  ▁
  ████▇█▆▆▆▄██▅▃▃▄██▁▅▄▄▁████▇▅▅▅▄▄▅▅▅▁▄▅▅▅▅▅▃▄▄▄▄▄▃▄▄▃▄▄▄▁▄▄ █
  9.01 ns      Histogram: log(frequency) by time      15.5 ns <

 Memory estimate: 0 bytes, allocs estimate: 0.

julia> show(stdout, MIME("text/plain"), @benchmark f4(10.0)); println()
BenchmarkTools.Trial: 10000 samples with 999 evaluations per sample.
 Range (min … max):  8.841 ns … 114.111 ns  ┊ GC (min … max): 0.00% … 0.00%
 Time  (median):     9.141 ns               ┊ GC (median):    0.00%
 Time  (mean ± σ):   9.885 ns ±   2.716 ns  ┊ GC (mean ± σ):  0.00% ± 0.00%

  █▇█▃▂        ▃     ▆▅▂                                      ▂
  ██████████▇▇▆██▇▇▆██████▇▇▇▆▆▆▇▅▆▅▅▅▇▆▆▆▆▆▅▆▆▆▅▆▆▅▄▁▆▅▅▅▆▄▆ █
  8.84 ns      Histogram: log(frequency) by time      17.4 ns <

 Memory estimate: 0 bytes, allocs estimate: 0.

here’s the above again, with release on the same computer:

julia> show(stdout, MIME("text/plain"), @benchmark f1($10.0)); println()
BenchmarkTools.Trial: 10000 samples with 1 evaluation per sample.
 Range (min … max):  160.846 μs … 742.954 μs  ┊ GC (min … max): 0.00% … 0.00%
 Time  (median):     173.678 μs               ┊ GC (median):    0.00%
 Time  (mean ± σ):   190.036 μs ±  35.612 μs  ┊ GC (mean ± σ):  0.00% ± 0.00%

  ▆█▇▅▄▃▃▂▃▄▅▃▃▂▃▄▄▂▂▂▁▁▂▁▁▁▁ ▁ ▃▄▃▂▂▁                     ▁    ▂
  ██████████████████████████████████████▇█▇▇▆▆▅▆▅▅▄▃▄▄▄▃▅▅▅██▄▆ █
  161 μs        Histogram: log(frequency) by time        311 μs <

 Memory estimate: 32 bytes, allocs estimate: 2.

julia> show(stdout, MIME("text/plain"), @benchmark f2($10.0)); println()
BenchmarkTools.Trial: 10000 samples with 4 evaluations per sample.
 Range (min … max):  7.054 μs … 645.076 μs  ┊ GC (min … max): 0.00% … 81.21%
 Time  (median):     8.277 μs               ┊ GC (median):    0.00%
 Time  (mean ± σ):   9.690 μs ±  24.047 μs  ┊ GC (mean ± σ):  9.79% ±  4.01%

  ▂▄▅▃▂▂▂▂▄▇█▇▅▃▃▄▃▂▂▁▂▁▁▁▁   ▁▂▂                             ▂
  █████████████████████████████████▇▇▇▇▇▇▇▇▆▆▅▅▆▅▅▅▅▅▅▅▄▂▄▅▄▄ █
  7.05 μs      Histogram: log(frequency) by time      13.8 μs <

 Memory estimate: 7.88 KiB, allocs estimate: 3.

julia> show(stdout, MIME("text/plain"), @benchmark f3($10.0)); println()
BenchmarkTools.Trial: 10000 samples with 5 evaluations per sample.
 Range (min … max):  5.780 μs … 99.489 μs  ┊ GC (min … max): 0.00% … 0.00%
 Time  (median):     6.541 μs              ┊ GC (median):    0.00%
 Time  (mean ± σ):   6.771 μs ±  1.322 μs  ┊ GC (mean ± σ):  0.00% ± 0.00%

        ▆█▃▁    ▁▁                                           ▁
  █▄▃▁▄▃████▇██████▇▇▇▇▇▇▇▆▇▆▇▇▇▇▅▇█▇▇▆▆▆▆▆▆▆▆▄▆▆▅▆▅▄▅▆▃▅▅▆▆ █
  5.78 μs      Histogram: log(frequency) by time     11.7 μs <

 Memory estimate: 0 bytes, allocs estimate: 0.

julia> show(stdout, MIME("text/plain"), @benchmark f4($10.0)); println()
BenchmarkTools.Trial: 10000 samples with 5 evaluations per sample.
 Range (min … max):  5.787 μs … 36.082 μs  ┊ GC (min … max): 0.00% … 0.00%
 Time  (median):     6.555 μs              ┊ GC (median):    0.00%
 Time  (mean ± σ):   6.893 μs ±  1.067 μs  ┊ GC (mean ± σ):  0.00% ± 0.00%

         █▄▂▁                          ▁                     ▁
  ▄▃▁▁▁▃▆███████▇██▇▇█████▇██▇███████▇██▇▇▇▆▆▆▆▇▇▅▆▅▆▇▅▅▅▆▆▅ █
  5.79 μs      Histogram: log(frequency) by time     11.5 μs <

 Memory estimate: 0 bytes, allocs estimate: 0.

julia> # now without the $ for full compilation magic

julia> show(stdout, MIME("text/plain"), @benchmark f1(10.0)); println()
BenchmarkTools.Trial: 10000 samples with 1 evaluation per sample.
 Range (min … max):  160.282 μs …  2.132 ms  ┊ GC (min … max): 0.00% … 0.00%
 Time  (median):     169.225 μs              ┊ GC (median):    0.00%
 Time  (mean ± σ):   180.205 μs ± 41.184 μs  ┊ GC (mean ± σ):  0.00% ± 0.00%

  ▆█▆▄▄▃▃▃▃▅▆▅▄▃▂▂▁▁▁             ▁                            ▂
  ██████████████████████▇▇▆█▇▆▆▇▇██▇▇▆▆▅▆▅▄▅▅▄▆▅▄▄▄▄▆▃▅▃▄▂▂▄▆▆ █
  160 μs        Histogram: log(frequency) by time       307 μs <

 Memory estimate: 32 bytes, allocs estimate: 2.

julia> show(stdout, MIME("text/plain"), @benchmark f2(10.0)); println()
BenchmarkTools.Trial: 10000 samples with 4 evaluations per sample.
 Range (min … max):  6.568 μs … 639.685 μs  ┊ GC (min … max): 0.00% … 83.01%
 Time  (median):     7.432 μs               ┊ GC (median):    0.00%
 Time  (mean ± σ):   9.166 μs ±  18.370 μs  ┊ GC (mean ± σ):  8.23% ±  4.27%

     ▆█▇▄▃▃▁▃▂▁▄▅▄▂▂▃▂▁  ▁▃▄▃▁                                ▂
  ▄▄▁██████████████████████████████▇▇█▇▆▇▇▆▆▅▆▅▆▅▅▄▅▄▄▄▄▄▄▄▄▅ █
  6.57 μs      Histogram: log(frequency) by time      15.9 μs <

 Memory estimate: 7.88 KiB, allocs estimate: 3.

julia> show(stdout, MIME("text/plain"), @benchmark f3(10.0)); println()
BenchmarkTools.Trial: 10000 samples with 6 evaluations per sample.
 Range (min … max):  5.780 μs … 174.387 μs  ┊ GC (min … max): 0.00% … 0.00%
 Time  (median):     6.538 μs               ┊ GC (median):    0.00%
 Time  (mean ± σ):   7.630 μs ±   2.717 μs  ┊ GC (mean ± σ):  0.00% ± 0.00%

       █▅▂▁▁▁▁▁   ▁    ▂  ▁   ▃                   ▂▅          ▁
  ▅▁▁▁▃██████████████████████████████▇▇▇▇▆▆▇▆▆▆▅▅▅██▆▆▅▆▄▄▆▅▇ █
  5.78 μs      Histogram: log(frequency) by time      13.3 μs <

 Memory estimate: 0 bytes, allocs estimate: 0.

julia> show(stdout, MIME("text/plain"), @benchmark f4(10.0)); println()
BenchmarkTools.Trial: 10000 samples with 6 evaluations per sample.
 Range (min … max):  5.836 μs … 39.879 μs  ┊ GC (min … max): 0.00% … 0.00%
 Time  (median):     6.549 μs              ┊ GC (median):    0.00%
 Time  (mean ± σ):   6.889 μs ±  1.223 μs  ┊ GC (mean ± σ):  0.00% ± 0.00%

        █▃▁  ▁▁▁▂                  ▂                         ▁
  ▃▁▁▁▁████████████▇██▇▇▇▆▇▇▇▇▇▇▇▇▇██▆▆▆▆▆▅▆▆▅▅▆▅▅▅▅▅▅▅▅▄▅▄▇ █
  5.84 μs      Histogram: log(frequency) by time     12.1 μs <

 Memory estimate: 0 bytes, allocs estimate: 0.

You should also try with just a simple loop (wrapped in a function, of course).

I don’t think there’s going to be any way to beat the compiler optimizing the entire loop away

(on release):

julia> f(a) = (for _=1:1000 a=exp(a) end; a)
f (generic function with 1 method)

julia> @benchmark f(10.)
BenchmarkTools.Trial: 10000 samples with 5 evaluations per sample.
 Range (min … max):  5.788 μs … 221.824 μs  ┊ GC (min … max): 0.00% … 0.00%
 Time  (median):     7.374 μs               ┊ GC (median):    0.00%
 Time  (mean ± σ):   7.333 μs ±   2.458 μs  ┊ GC (mean ± σ):  0.00% ± 0.00%

  ▁       ▆▇▂       ▁█▂▁  ▁     ▂     ▂    ▁▂ ▁               ▂
  █▇▅▁▁▄▃▃████▇▆▄▅▅▃█████▇███▇▇███▇█▇▇██▇▇▇██▇█▇▆▆▇▆▆▄▆▆▃▄▅▄▄ █
  5.79 μs      Histogram: log(frequency) by time      10.7 μs <

 Memory estimate: 0 bytes, allocs estimate: 0.

Sure, but that’s not happening in reality, is it? You do not know what value that you will call f with in any non-trivial case. And any way, the same optimization should probably happen with the simple loop as well. If it doesn’t, that deserves some investigation.

When benchmarking stuff like this, I would always recommend comparing with a baseline loop (which is normally also the optimal solution).

I think foldl may be able to guarantee its function will not change for each iteration of the loop, while a simple for loop may run something different each time. I wouldn’t be surprised if foldl was able to compile itself down to nothing better than an equivalent for loop, since it has op in the call signature: _foldl_impl(op::OP, init, itr) where {OP}.

You can do the same thing yourself with a simple loop.

Exactly.

I stand corrected. apparently it can compile the for loop away as well:

julia> f1(a) = foldl((x,_) -> exp(x), repeated(nothing, 1000); init=a)
f1 (generic function with 1 method)

julia> f2(a) = (for _=1:1000 a=exp(a) end; a)
f2 (generic function with 1 method)

julia> @benchmark f1(10.)
BenchmarkTools.Trial: 10000 samples with 999 evaluations per sample.
 Range (min … max):  8.914 ns … 215.865 ns  ┊ GC (min … max): 0.00% … 0.00%
 Time  (median):     9.201 ns               ┊ GC (median):    0.00%
 Time  (mean ± σ):   9.477 ns ±   3.195 ns  ┊ GC (mean ± σ):  0.00% ± 0.00%

  █▇▄█▄▂▄▂    ▃▂      ▂▃                                      ▂
  ████████▇█▇▇████▆▆▇████▇▇▇▅▆▅█▇▆▆▆▆▅▅▅▆▅▆▅▅▄▆▃▆▅▅▅▆▄▁▅▅▅▄▅▅ █
  8.91 ns      Histogram: log(frequency) by time      14.7 ns <

 Memory estimate: 0 bytes, allocs estimate: 0.

julia> @benchmark f2(10.)
BenchmarkTools.Trial: 10000 samples with 999 evaluations per sample.
 Range (min … max):  8.915 ns … 143.037 ns  ┊ GC (min … max): 0.00% … 0.00%
 Time  (median):     9.076 ns               ┊ GC (median):    0.00%
 Time  (mean ± σ):   9.862 ns ±   2.721 ns  ┊ GC (mean ± σ):  0.00% ± 0.00%

  ██▂▆▂▂▃     ▂▂     ▇▆▄                                      ▂
  ████████▆▇▆▇███▆▆▆▆█████▆▇▇▆▆▅▄▆▅▅▅▆▅▄▄▄▅▄▅▅▅▅▄▄▄▅▄▄▄▄▄▅▅▅▃ █
  8.91 ns      Histogram: log(frequency) by time      14.9 ns <

 Memory estimate: 0 bytes, allocs estimate: 0.

(sorry for the long gap between replies; I had just started rebuilding a new master…)

I think it’s fantastic how these functional patterns can be efficiently implemented with with pretty simple Julia code. But it also highlights how “just roll your own simple loop implementation” is so frequently a viable strategy in Julia.

Not at all, I’m much slower​:grin:

Is the ability to hoist away the loop the part you actually intend to benchmark here though? The loop is only removable when the argument is a constant.

Keep in mind, this is only possible because of the way you’ve benchmarked it, where the argument 10.0 is a compile-time constant, so it just hoists the evaluations out of the loop. If you care about how long it takes to actually compute this though, you’ll likely need to make it so that the compiler can’t infer 10.0 as a constant. E.g.

julia> let x = Ref(10.0)
           @benchmark f1($x[])
       end
BenchmarkTools.Trial: 10000 samples with 10 evaluations per sample.
 Range (min … max):  2.231 μs …  4.530 μs  ┊ GC (min … max): 0.00% … 0.00%
 Time  (median):     2.260 μs              ┊ GC (median):    0.00%
 Time  (mean ± σ):   2.268 μs ± 70.451 ns  ┊ GC (mean ± σ):  0.00% ± 0.00%

     ▂█▆                                                      
  ▂▃▆████▅▃▃▃▂▂▂▁▂▁▂▁▁▂▁▁▂▁▂▂▁▁▂▁▁▁▁▁▁▁▂▂▁▁▁▁▁▂▁▂▁▂▁▁▂▂▂▂▂▂▂ ▃
  2.23 μs        Histogram: frequency by time        2.57 μs <

 Memory estimate: 0 bytes, allocs estimate: 0.

julia> let x = Ref(10.0)
           @benchmark f2($x[])
       end
BenchmarkTools.Trial: 10000 samples with 10 evaluations per sample.
 Range (min … max):  2.107 μs …   6.654 μs  ┊ GC (min … max): 0.00% … 0.00%
 Time  (median):     2.266 μs               ┊ GC (median):    0.00%
 Time  (mean ± σ):   2.294 μs ± 190.517 ns  ┊ GC (mean ± σ):  0.00% ± 0.00%

  ▂ ▁  ▄▇█▆▃▁                                                 ▂
  ███▁███████▅▅▅▅▅▄▅▅▅▇▆▆▇▆█▇▄▅▅▄▄▅▅▅▆▃▄▄▅▄▁▁▃▃▁▄▄▄▅▄▄▃▁▄▁▄▃▇ █
  2.11 μs      Histogram: log(frequency) by time      3.41 μs <

 Memory estimate: 0 bytes, allocs estimate: 0.

the compiler will propagate some constants for you, so there are some cases it gets completely taken care of beforehand

julia> f(a, b, c) = (k=1000+b; foldl((x,_) -> exp(x), repeated(nothing, k-b); init=a+c))
f (generic function with 2 methods)

julia> @benchmark f(10., 10, 0.)
BenchmarkTools.Trial: 10000 samples with 999 evaluations per sample.
 Range (min … max):   8.298 ns … 692.584 ns  ┊ GC (min … max): 0.00% … 0.00%
 Time  (median):     10.393 ns               ┊ GC (median):    0.00%
 Time  (mean ± σ):   10.740 ns ±   7.561 ns  ┊ GC (mean ± σ):  0.00% ± 0.00%

      ▅▇▇▄▃▂▅▁  ▅▄▂▁▂▅▂▁  ▃█▄▂                                 ▂
  █▃▁▁████████▇▇██████████████▇▇█▆▇▇▆▅▇▇▇▆▅▅▅▄▆▆▆▆▅▅▄▃▅▅▄▅▄▄▃▃ █
  8.3 ns        Histogram: log(frequency) by time      16.8 ns <

 Memory estimate: 0 bytes, allocs estimate: 0.

also you can put $ directly on a value and it’ll still hide it for you:

julia> @benchmark f($10., 10, 0.)
BenchmarkTools.Trial: 10000 samples with 5 evaluations per sample.
 Range (min … max):  6.573 μs … 86.563 μs  ┊ GC (min … max): 0.00% … 0.00%
 Time  (median):     7.549 μs              ┊ GC (median):    0.00%
 Time  (mean ± σ):   8.105 μs ±  2.508 μs  ┊ GC (mean ± σ):  0.00% ± 0.00%

  ▁  ▁  █▄▁▂▅▂▁▁▁▁      ▃                                    ▁
  █▃▂█▇▅████████████▇▇▇██▇▆▆▆▆▆▆▅▅▆▆▅▅▅▆▄▄▅▅▄▅▄▅▆▅▅▅▅▅▄▃▄▃▃▄ █
  6.57 μs      Histogram: log(frequency) by time     14.8 μs <

 Memory estimate: 0 bytes, allocs estimate: 0.

Maybe Core.donotdelete might come in handy?