# Avoid ambiguous function type allocation without losing abstract type

**URL:** <https://discourse.julialang.org/t/avoid-ambiguous-function-type-allocation-without-losing-abstract-type/59040>\
**Category:** Performance\
**Tags:** struct, type-stability\
**Created:** [April 11, 2021, 3:29pm UTC](https://discourse.julialang.org/t/avoid-ambiguous-function-type-allocation-without-losing-abstract-type/59040 "2021-04-11T15:29:57Z")\
**Posts on this page:** 6\
**Page:** 1

<div class="post-metadata">

**Author:** ![weymouth](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/weymouth/32/15839_2.png) [@weymouth](https://discourse.julialang.org/u/weymouth)\
**Post date:** [April 11, 2021, 3:29pm UTC](https://discourse.julialang.org/t/avoid-ambiguous-function-type-allocation-without-losing-abstract-type/59040/1 "2021-04-11T15:29:57Z")

</div>

I’m using a multiple dispatch on an abstract type (`AbstractBody`) to switch between two behaviours. I need to store a performance critical function in one case, but don’t need to store anything in the other case.

I’ve implemented a version which avoids ambiguous function type allocations, but I lose this specification when I use a struct to hold any AbstractBody.

```julia
abstract type AbstractBody end

struct NoBody<:AbstractBody end

struct FuncBody{F<:Function}<:AbstractBody
    sdf::F
end

struct SimAmbiguous
    body::AbstractBody # this works even when there is `NoBody`
end

struct SimUnambiguous{F<:Function}
    body::FuncBody{F} # This locks me into needing a function
end

function circle(ambiguous; radius = 8.0)
    sdf(x) = norm2(x) - radius
    ambiguous && return SimAmbiguous(FuncBody(sdf))
    return SimUnambiguous(FuncBody(sdf))
end
ambiguous = circle(true) # slow
unambiguous = circle(false) # fast

julia> @btime $ambiguous.body.sdf(x) setup=(x=rand(3));
  73.024 ns (2 allocations: 32 bytes)

julia> @btime $unambiguous.body.sdf(x) setup=(x=rand(3));
  10.431 ns (0 allocations: 0 bytes)

```

Do I need to give up on the abstract type approach? If so, how do I handle when there is `NoBody` to use in `SimUnambiguous`? Some default function?

---

<div class="post-metadata">

**Author:** ![weymouth](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/weymouth/32/15839_2.png) [@weymouth](https://discourse.julialang.org/u/weymouth)\
**Post date:** [April 11, 2021, 6:21pm UTC](https://discourse.julialang.org/t/avoid-ambiguous-function-type-allocation-without-losing-abstract-type/59040/2 "2021-04-11T18:21:41Z")

</div>

I’m not sure why this wasn’t working before, maybe something to do with the benchmark. Here’s a more complete example which works fine.

```julia
using BenchmarkTools
using LinearAlgebra: norm2

abstract type AbstractBody end
abstract type NoBody <: AbstractBody end

struct FuncBody{F1<:Function,F2<:Function} <: AbstractBody
    sdf::F1
    map::F2
    function FuncBody(sdf,map=(x,t)->x)
        comp(x,t) = sdf(map(x,t),t)
        new{typeof(comp),typeof(map)}(comp, map)
    end
end

struct SimAmbiguous{A<:AbstractArray,B<:AbstractBody}
    a::A
    body::B
end

function nsphere(radius = 8.0, n = 2)
    sdf(x,t) = norm2(x) - radius
    map(x,t) = (x[1]-=radius*(1-cos(t/radius)); x)
    return SimAmbiguous(zeros(ntuple(i->Int(radius), n)), FuncBody(sdf,map))
end

measure!(sim) = measure!(sim.a, sim.body)
function measure!(a::Array{Float64,N}, body; t=0) where N
    x = zeros(N) # here's the single allocation.
    for I in CartesianIndices(a)
        @. x = I.I-1.4
        a[I] = body.sdf(x,t)
    end
end
function measure!(a::Array{Float64,N}, body::NoBody; t=0) where N end

julia> @btime measure!(sim) setup=(sim=nsphere(2^6,2))
  76.061 μs (1 allocation: 96 bytes)

```

The one allocation is a small price to pay for the speed up relative to using tuples for the `sdf`.

---

<div class="post-metadata">

**Author:** ![FPGro](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/fpgro/32/20822_2.png) [@FPGro](https://discourse.julialang.org/u/FPGro)\
**Post date:** [April 11, 2021, 6:33pm UTC](https://discourse.julialang.org/t/avoid-ambiguous-function-type-allocation-without-losing-abstract-type/59040/3 "2021-04-11T18:33:13Z")

</div>

Cool, but what happened to `NoBody`? Did you just drop it or does it work fine despite the ambiguity?

---

<div class="post-metadata">

**Author:** ![lmiq](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/lmiq/32/18314_2.png) [@lmiq](https://discourse.julialang.org/u/lmiq)\
**Post date:** [April 11, 2021, 6:35pm UTC](https://discourse.julialang.org/t/avoid-ambiguous-function-type-allocation-without-losing-abstract-type/59040/4 "2021-04-11T18:35:58Z")

</div>

> [@weymouth](#):
>
> ```julia
> struct SimAmbiguous
> a::Array
> body:: AbstractBody
> end
> 
> ```

Just for curiosity, couldn’t this be written as:

```julia
struct SimAmbiguous{A<:AbstractArray,B<:AbstractBody}
    a::A
    body::B
end

```

?  
seems safer from a type stability point of view, if it works.

---

<div class="post-metadata">

**Author:** ![weymouth](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/weymouth/32/15839_2.png) [@weymouth](https://discourse.julialang.org/u/weymouth)\
**Post date:** [April 11, 2021, 7:05pm UTC](https://discourse.julialang.org/t/avoid-ambiguous-function-type-allocation-without-losing-abstract-type/59040/5 "2021-04-11T19:05:35Z")

</div>

Yes, it works fine. I’ve put it back into the example.

---

<div class="post-metadata">

**Author:** ![weymouth](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/weymouth/32/15839_2.png) [@weymouth](https://discourse.julialang.org/u/weymouth)\
**Post date:** [April 11, 2021, 7:08pm UTC](https://discourse.julialang.org/t/avoid-ambiguous-function-type-allocation-without-losing-abstract-type/59040/6 "2021-04-11T19:08:00Z")

</div>

It does! Good idea.
