# Optimal way to broadcast over a container of funcs

**URL:** <https://discourse.julialang.org/t/optimal-way-to-broadcast-over-a-container-of-funcs/116156>\
**Category:** Performance\
**Created:** [June 24, 2024, 5:49pm UTC](https://discourse.julialang.org/t/optimal-way-to-broadcast-over-a-container-of-funcs/116156 "2024-06-24T17:49:14Z")\
**Posts on this page:** 12\
**Page:** 1

<div class="post-metadata">

**Author:** ![miguelborrero](https://avatars.discourse-cdn.com/v4/letter/m/eb9ed0/32.png) [@miguelborrero](https://discourse.julialang.org/u/miguelborrero)\
**Post date:** [June 24, 2024, 5:49pm UTC](https://discourse.julialang.org/t/optimal-way-to-broadcast-over-a-container-of-funcs/116156/1 "2024-06-24T17:49:14Z")

</div>

Hi there,

I have a vector (more generally: a list) of functions the map a vector to a vector and also a matrix where each row is to be populated by each of the functions. See below an example:

```julia
function test()
    f_vec = [x -> x.^2 .+ i for i in 1:10]
    grid = collect(1:12)
    cont = zeros(length(f_vec), length(grid))
    
    for f_ind in 1:length(f_vec)
        cont[f_ind, :] .= f_vec[f_ind](grid)
    end

end

```

I get the following benchmarking results:

 ![Captura de pantalla 2024-06-24 a la(s) 11.05.50](https://global.discourse-cdn.com/julialang/original/3X/d/9/d9ecda786d4c22e0dfbf81791360ad9be17e2d86.png)

Which not seem ideal. Is there an optimal way of doing so?

Thanks a lot in advance!

---

<div class="post-metadata">

**Author:** ![GunnarFarneback](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/gunnarfarneback/32/1827_2.png) [@GunnarFarneback](https://discourse.julialang.org/u/GunnarFarneback)\
**Post date:** [June 24, 2024, 5:59pm UTC](https://discourse.julialang.org/t/optimal-way-to-broadcast-over-a-container-of-funcs/116156/2 "2024-06-24T17:59:53Z")

</div>

You will not get a very meaningful benchmarking result if you access non-const globals inside your function. I would start by making `cont` and `grid` arguments to your test function.

---

<div class="post-metadata">

**Author:** ![miguelborrero](https://avatars.discourse-cdn.com/v4/letter/m/eb9ed0/32.png) [@miguelborrero](https://discourse.julialang.org/u/miguelborrero)\
**Post date:** [June 24, 2024, 6:06pm UTC](https://discourse.julialang.org/t/optimal-way-to-broadcast-over-a-container-of-funcs/116156/3 "2024-06-24T18:06:23Z")

</div>

Yep, I just modified the post to reflect this. Thanks.

---

<div class="post-metadata">

**Author:** ![mikmoore](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/mikmoore/32/31109_2.png) [@mikmoore](https://discourse.julialang.org/u/mikmoore)\
**Post date:** [June 24, 2024, 6:25pm UTC](https://discourse.julialang.org/t/optimal-way-to-broadcast-over-a-container-of-funcs/116156/4 "2024-06-24T18:25:01Z")

</div>

Try either of these (the latter using `map` rather than allocate-and-fill)

```julia
function test2()
    f_vec = [x -> x^2 + i for i in 1:10]
    grid = collect(1:12)
    cont = zeros(length(f_vec), length(grid))
    
    for f_ind in 1:length(f_vec)
        cont[f_ind, :] .= f_vec[f_ind].(grid)
    end
    return cont
end

function test3()
    f_vec = [x -> x^2 + i for i in 1:10]
    grid = collect(1:12)

    cont = map(((f,g),) -> f(g), Iterators.product(f_vec, grid))
    return cont
end

```

Notably, I changed your functions to _not_ operate on vectors. Instead, they operate on scalars and I broadcast (or map) the function itself over the vector.

I’ll also discourage you from using `collect` in most situations. `grid = 1:12` would have worked fine and would save memory and time.

I’ll remark that this is performant because your functions are all related closures. If you instead did a big list of dissimilar functions (e.g., `f_vec = [sin, cos, tan, abs, exp]` you’d have dynamic dispatch to contend with and [a different pattern](https://docs.julialang.org/en/v1/manual/performance-tips/#kernel-functions) would be needed.

---

<div class="post-metadata">

**Author:** ![DNF](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/dnf/32/10191_2.png) [@DNF](https://discourse.julialang.org/u/DNF)\
**Post date:** [June 24, 2024, 6:30pm UTC](https://discourse.julialang.org/t/optimal-way-to-broadcast-over-a-container-of-funcs/116156/5 "2024-06-24T18:30:26Z")

</div>

Keep in mind that you are now also benchmarking the time it takes to build the vector of functions and the vector of function inputs. If you want to time only the application, you should create those outside the `test` function and pass them as inputs.

> [@miguelborrero](#):
>
> I have a vector (more generally: a list)

BTW, there isn’t really a ‘list’ container that is more general than `Vector`. The thing that you would call a ‘list’ in e.g. Python, is just a `Vector{Any}` in Julia.

---

<div class="post-metadata">

**Author:** ![Dan](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/dan/32/42581_2.png) [@Dan](https://discourse.julialang.org/u/Dan)\
**Post date:** [June 24, 2024, 6:37pm UTC](https://discourse.julialang.org/t/optimal-way-to-broadcast-over-a-container-of-funcs/116156/6 "2024-06-24T18:37:02Z")

</div>

This looks short and easy to read:

```julia
function test4()
    f_vec = [x -> x^2 + i for i in 1:10]
    grid = collect(1.0:1.0:12.0)
    [f(g) for f in f_vec, g in grid]
end

```

BTW no need to `collect` the range in `grid`.

---

<div class="post-metadata">

**Author:** ![miguelborrero](https://avatars.discourse-cdn.com/v4/letter/m/eb9ed0/32.png) [@miguelborrero](https://discourse.julialang.org/u/miguelborrero)\
**Post date:** [June 24, 2024, 10:56pm UTC](https://discourse.julialang.org/t/optimal-way-to-broadcast-over-a-container-of-funcs/116156/7 "2024-06-24T22:56:31Z")

</div>

Agreed, I just wanted to refer to the fact that it need to be a vector. Just a container or list of some form like a tuple. But thanks for the comment.

---

<div class="post-metadata">

**Author:** ![miguelborrero](https://avatars.discourse-cdn.com/v4/letter/m/eb9ed0/32.png) [@miguelborrero](https://discourse.julialang.org/u/miguelborrero)\
**Post date:** [June 24, 2024, 11:48pm UTC](https://discourse.julialang.org/t/optimal-way-to-broadcast-over-a-container-of-funcs/116156/8 "2024-06-24T23:48:10Z")

</div>

Thanks a lot for the suggestions. However, I had the function operating on vectors on purpose since I found that the banchmarking results where better. See the example below:

```julia
function test()

    u = zeros(Float64, 30)
    a = [1.0, 0.0, 0.0, 0.0, 0.0]

    function hermite5_cdf_temp(a::AbstractArray{TV, 1}, μ::TV, σ::TV) where TV
        func(x) = σ * Polynomial(a)((x - μ) / σ) * pdf.(Normal(μ, σ), x) / 24.0 + cdf(Normal(μ, σ), x)
        return func
    end

    hermite5_cdf_temp(a, 1.0, 1.0).(u)

end

```

Which benchmarks to:

 ![Captura de pantalla 2024-06-24 a la(s) 16.47.27](https://global.discourse-cdn.com/julialang/original/3X/0/7/075d97f51a81ff02ae3ebd35b1fa903f9e42b327.png)

and then compare to this, which is the same function but operating on vectors:

```julia
function test()

    u = zeros(Float64, 30)
    a = [1.0, 0.0, 0.0, 0.0, 0.0]

    function hermite5_cdf_temp(a::AbstractArray{TV, 1}, μ::TV, σ::TV) where TV
        func(x) = σ * Polynomial(a).((x .- μ) / σ) .* pdf.(Normal(μ, σ), x) / 24.0 .+ cdf.(Normal(μ, σ), x)
        return func
    end

    hermite5_cdf_temp(a, 1.0, 1.0)(u)

end

```

and benchmarks to:

 ![Captura de pantalla 2024-06-24 a la(s) 16.47.47](https://global.discourse-cdn.com/julialang/original/3X/d/c/dcc6f921f0c520585235b76d41f3c2fe842a0495.png)

Therefore I would like to keep it acting on vectors, thanks! Any ideas given this?

---

<div class="post-metadata">

**Author:** ![stevengj](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/stevengj/32/71_2.png) [@stevengj](https://discourse.julialang.org/u/stevengj)\
**Post date:** [June 25, 2024, 12:05am UTC](https://discourse.julialang.org/t/optimal-way-to-broadcast-over-a-container-of-funcs/116156/9 "2024-06-25T00:05:03Z")

</div>

> [@mikmoore](#):
>
> I’ll remark that this is performant because your functions are all related closures. If you instead did a big list of dissimilar functions (e.g., `f_vec = [sin, cos, tan, abs, exp]` you’d have dynamic dispatch to contend with and [a different pattern](https://docs.julialang.org/en/v1/manual/performance-tips/#kernel-functions) would be needed.

In particular, an array/vector of functions is an abstractly typed container, and really inhibits Julia’s type inference. You might be better off with careful use of a tuple of functions (or using fancy tricks like [FunctionWrappers.jl](https://github.com/yuyichao/FunctionWrappers.jl)), if you don’t want to re-think your whole approach.

---

<div class="post-metadata">

**Author:** ![Dan](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/dan/32/42581_2.png) [@Dan](https://discourse.julialang.org/u/Dan)\
**Post date:** [June 25, 2024, 12:39am UTC](https://discourse.julialang.org/t/optimal-way-to-broadcast-over-a-container-of-funcs/116156/10 "2024-06-25T00:39:08Z")

</div>

In Julia the call operation can be overloaded for types, this allows a nice method to get the functionality in the original post:

```julia
using Polynomials, Distributions

struct Hermite5
    a :: Vector{Float64}
    σ :: Float64
    μ :: Float64
end

(h::Hermite5)(x) = h.σ * Polynomial(h.a).((x .- h.μ) / h.σ) .* 
  pdf.(Normal(h.μ, h.σ), x) / 24.0 .+ cdf.(Normal(h.μ, h.σ), x)

function test2()
    u = zeros(Float64, 30)
    a = [1.0, 0.0, 0.0, 0.0, 0.0]
    h = Hermite5(a,1.0,1.0)

    h.(u)
end

```

This method might not only look better, but also perform better because of problems with captured variables in closures when handling functions.

---

<div class="post-metadata">

**Author:** ![miguelborrero](https://avatars.discourse-cdn.com/v4/letter/m/eb9ed0/32.png) [@miguelborrero](https://discourse.julialang.org/u/miguelborrero)\
**Post date:** [June 25, 2024, 12:51am UTC](https://discourse.julialang.org/t/optimal-way-to-broadcast-over-a-container-of-funcs/116156/11 "2024-06-25T00:51:31Z")

</div>

It did not perform better but thanks for the comment!

---

<div class="post-metadata">

**Author:** ![Dan](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/dan/32/42581_2.png) [@Dan](https://discourse.julialang.org/u/Dan)\
**Post date:** [June 25, 2024, 1:01am UTC](https://discourse.julialang.org/t/optimal-way-to-broadcast-over-a-container-of-funcs/116156/12 "2024-06-25T01:01:26Z")

</div>

The trick would be to optimize the function application by removing repeated generation/allocation of structures:

```julia
struct Hermite6
    a :: Polynomial{Float64, :x}
    D :: Normal{Float64}
    μ :: Float64
    σ :: Float64
    Hermite6(a,μ,σ) = new(Polynomial(a), Normal(μ, σ), μ, σ)
end

(h::Hermite6)(x) = h.σ * (h.a).((x .- h.μ) / h.σ) .* 
  pdf.(h.D, x) / 24.0 .+ cdf.(h.D, x)

function test3()
    u = zeros(Float64, 30)
    a = [1.0, 0.0, 0.0, 0.0, 0.0]
    h = Hermite6(a,1.0,1.0)

    h.(u)
end

```

In the OP, everytime the function is called a `Normal` and a `Polynomial` struct needs to be created (and benchmarking shows about 3x allocation).
