# Multiple Methods

**URL:** <https://discourse.julialang.org/t/multiple-methods/119006>\
**Category:** Performance\
**Created:** [September 3, 2024, 7:34pm UTC](https://discourse.julialang.org/t/multiple-methods/119006 "2024-09-03T19:34:06Z")\
**Posts on this page:** 11\
**Page:** 1

<div class="post-metadata">

**Author:** ![Mirsad\_Cosovic](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/mirsad_cosovic/32/9887_2.png) [@Mirsad\_Cosovic](https://discourse.julialang.org/u/Mirsad_Cosovic)\
**Post date:** [September 3, 2024, 7:34pm UTC](https://discourse.julialang.org/t/multiple-methods/119006/1 "2024-09-03T19:34:06Z")

</div>

Hello,

I have multiple methods with the same name but different argument types. When I add a fourth method with a distinct argument type, I observe a significant increase in memory allocations during the first run. I understand that this likely increases compiler workload. I’m curious why three methods seem optimal, and what might be causing the increase in allocations with the fourth. Is it a valid approach to add extra dummy variables with different types to help the compiler handle this more efficiently?

The following toy example illustrates the behavior I see when I run more complex code and measure the time for the first run. I realize the example may not be ideal and that I might be measuring time in the wrong place, but the main point is understanding why this happens. Specifically, is the compiler designed to behave this way?

```julia
module Test
    struct Test1
        a::Array{Float64,1}
    end
    struct Test2
        a::Array{Int64,1}
    end
    struct Test3
        a::Float64
    end
    struct Test4
        a::Int64
    end

    function form(a::Array{Float64,1})
        return Test1(a)
    end
    function form(a::Array{Int64,1})
        return Test2(a)
    end
    function form(a::Float64)
        return Test3(a)
    end
    function form(a::Int64)
        return Test4(a)
    end

    function solve(obj::Test1)
        obj.a + obj.a
    end
    function solve(obj::Test2)
        obj.a + obj.a
    end
    function solve(obj::Test3)
        obj.a + obj.a
    end
    function solve(obj::Test4)
        obj.a + obj.a
    end

    obj = form(fill(0.0, 10))
    @time solve(obj)
end

```

```julia
0.003744 seconds (1.10 k allocations: 78.141 KiB, 99.49% compilation time)

```

If I remove just one `solve` function, I get:

```julia
0.000003 seconds (1 allocation: 144 bytes)

```

---

<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:** [September 3, 2024, 7:48pm UTC](https://discourse.julialang.org/t/multiple-methods/119006/2 "2024-09-03T19:48:56Z")

</div>

> [@Mirsad\_Cosovic](#):
>
> `99.49% compilation time`

You’re just measuring the one-time cost of compilation. Don’t use `@time`, use BenchmarkTools.jl and use `@btime solve($obj)`

---

<div class="post-metadata">

**Author:** ![Mirsad\_Cosovic](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/mirsad_cosovic/32/9887_2.png) [@Mirsad\_Cosovic](https://discourse.julialang.org/u/Mirsad_Cosovic)\
**Post date:** [September 3, 2024, 7:52pm UTC](https://discourse.julialang.org/t/multiple-methods/119006/3 "2024-09-03T19:52:26Z")

</div>

I understand that if the increased allocations are simply a consequence of using the `@time` function, then that’s fine, and I’m open to that explanation. However, if that’s not the case, I’m still curious about why I’m seeing this significant increase in allocations. Could there be another reason for this behavior?

In the second case, I also use the `@time` function, but I only see one allocation.

---

<div class="post-metadata">

**Author:** ![Oscar\_Smith](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/oscar_smith/32/25343_2.png) [@Oscar\_Smith](https://discourse.julialang.org/u/Oscar_Smith)\
**Post date:** [September 3, 2024, 7:56pm UTC](https://discourse.julialang.org/t/multiple-methods/119006/4 "2024-09-03T19:56:08Z")

</div>

The allocation increaswe is also because it’s counting the allocations of compiling it.

---

<div class="post-metadata">

**Author:** ![Mirsad\_Cosovic](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/mirsad_cosovic/32/9887_2.png) [@Mirsad\_Cosovic](https://discourse.julialang.org/u/Mirsad_Cosovic)\
**Post date:** [September 3, 2024, 7:59pm UTC](https://discourse.julialang.org/t/multiple-methods/119006/5 "2024-09-03T19:59:57Z")

</div>

Certainly, but why does the number of allocations increase after the third function, yet adding a fifth function doesn’t cause further increases?

---

<div class="post-metadata">

**Author:** ![Satvik](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/satvik/32/20486_2.png) [@Satvik](https://discourse.julialang.org/u/Satvik)\
**Post date:** [September 3, 2024, 8:19pm UTC](https://discourse.julialang.org/t/multiple-methods/119006/6 "2024-09-03T20:19:31Z")

</div>

I tested this and got the same results. This doesn’t allocate much, in a fresh REPL:

```julia
julia> struct Test1
           a::Array{Float64,1}
       end

julia> struct Test2
           a::Array{Int64,1}
       end

julia> struct Test3
           a::Float64
       end

julia> struct Test4
           a::Int64
       end

julia> function form(a::Array{Float64,1})
           return Test1(a)
       end
form (generic function with 1 method)

julia> function form(a::Array{Int64,1})
           return Test2(a)
       end
form (generic function with 2 methods)

julia> function form(a::Float64)
           return Test3(a)
       end
form (generic function with 3 methods)

julia> function form(a::Int64)
           return Test4(a)
       end
form (generic function with 4 methods)

julia> function solve(obj::Test1)
           obj.a + obj.a
       end
solve (generic function with 1 method)

julia> function solve(obj::Test2)
           obj.a + obj.a
       end
solve (generic function with 2 methods)

julia> function solve(obj::Test3)
           obj.a + obj.a
       end
solve (generic function with 3 methods)

julia> obj = form(fill(0.0, 10))
Test1([0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0])

julia> @time solve(obj)
  0.000005 seconds (1 allocation: 144 bytes)

```

But in a new REPL, adding the definition

```julia
function solve(obj::Test4)
        obj.a + obj.a
end

```

before running suddenly results in

```julia
julia> @time solve(obj)
  0.031775 seconds (119.13 k allocations: 8.241 MiB, 99.95% compilation time)

```

I’m guessing there’s some clever compiler optimization when there are 3 or fewer methods of a function, but I’d also be curious to know what it is & how it works.

---

<div class="post-metadata">

**Author:** ![Mirsad\_Cosovic](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/mirsad_cosovic/32/9887_2.png) [@Mirsad\_Cosovic](https://discourse.julialang.org/u/Mirsad_Cosovic)\
**Post date:** [September 3, 2024, 8:45pm UTC](https://discourse.julialang.org/t/multiple-methods/119006/7 "2024-09-03T20:45:09Z")

</div>

Yes, the behavior is quite puzzling. When I add a dummy variable, the compiler seems to differentiate between the functions more effectively. Could the reason be related to the use of concrete types versus custom types?

```julia
module Test
    struct Test1
        a::Array{Float64,1}
    end
    struct Test2
        a::Array{Int64,1}
    end
    struct Test3
        a::Float64
    end
    struct Test4
        a::Int64
    end
    struct Test5
        a::String
    end

    function form(a::Array{Float64,1})
        return Test1(a)
    end
    function form(a::Array{Int64,1})
        return Test2(a)
    end
    function form(a::Float64)
        return Test3(a)
    end
    function form(a::Int64)
        return Test4(a)
    end

    function solve(obj::Test1, dummy::Array{Float64,1})
        obj.a + obj.a
    end
    function solve(obj::Test2, dummy::Array{Int64,1})
        obj.a + obj.a
    end
    function solve(obj::Test3, dummy::Float64)
        obj.a + obj.a
    end
    function solve(obj::Test4, dummy::Int64)
        obj.a + obj.a
    end

    obj = form(fill(0.0, 10))
    @time solve(obj, [0.0])
end

```

```julia
  0.000003 seconds (2 allocations: 208 bytes)

```

---

<div class="post-metadata">

**Author:** ![nsajko](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/nsajko/32/221187_2.png) [@nsajko](https://discourse.julialang.org/u/nsajko)\
**Post date:** [September 3, 2024, 9:48pm UTC](https://discourse.julialang.org/t/multiple-methods/119006/8 "2024-09-03T21:48:13Z")

</div>

> [@Mirsad\_Cosovic](#):
>
> When I add a fourth method with a distinct argument type

Maybe something related to `max_methods`. See the help for `Base.Experimental.@max_methods` and `Base.Experimental.@compiler_options`, and try playing around with the settings experimentally, if interested.

> [@Mirsad\_Cosovic](#):
>
> Is it a valid approach to add extra dummy variables with different types to help the compiler handle this more efficiently?

No, that would be unnecessary and, frankly, horrible.

> [@Mirsad\_Cosovic](#):
>
> is the compiler designed to behave this way?

The compiler is designed to do lots of stuff. If you want less compiling/optimization, choose that explicitly, using the above experimental settings, or using the documented command-line options.

---

<div class="post-metadata">

**Author:** ![ToucheSir](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/touchesir/32/14411_2.png) [@ToucheSir](https://discourse.julialang.org/u/ToucheSir)\
**Post date:** [September 4, 2024, 12:27am UTC](https://discourse.julialang.org/t/multiple-methods/119006/9 "2024-09-04T00:27:28Z")

</div>

> [@Satvik](#):
>
> I’m guessing there’s some clever compiler optimization when there are 3 or fewer methods of a function, but I’d also be curious to know what it is & how it works.

That’s correct, see the links in [Avoiding Vectors of Abstract Types - #20 by tim.holy](https://discourse.julialang.org/t/avoiding-vectors-of-abstract-types/61883/20) or [Elimination of (unnecessary) runtime dispatch allocations - #2 by mbauman](https://discourse.julialang.org/t/elimination-of-unnecessary-runtime-dispatch-allocations/113475/2) for more on this.

Solution-wise, I’m going to assume there are a finite number of possible `TestN` types. In that case, using a [sum types package](https://discourse.julialang.org/t/ann-moshi-v0-3-the-next-gen-mlstyle-gadt-extensible-pattern-matching-and-more/117613) like SumTypes.jl, DynamicSumTypes.jl, Moshi.jl, etc. would remove allocations related to type instability.

---

<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:** [September 4, 2024, 1:07am UTC](https://discourse.julialang.org/t/multiple-methods/119006/10 "2024-09-04T01:07:44Z")

</div>

This case is not about type stability or runtime dispatch, is something related to the compilation heuristic. Only relevant for the first run.

---

<div class="post-metadata">

**Author:** ![ToucheSir](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/touchesir/32/14411_2.png) [@ToucheSir](https://discourse.julialang.org/u/ToucheSir)\
**Post date:** [September 4, 2024, 2:05am UTC](https://discourse.julialang.org/t/multiple-methods/119006/11 "2024-09-04T02:05:27Z")

</div>

Are you sure those aren’t culprits? Because if I change `obj = form(fill(0.0, 10))` to

```julia
const obj = form(fill(0.0, 10))

```

or

```julia
obj::Test1 = form(fill(0.0, 10))

```

or

```julia
let obj = form(fill(0.0, 10))
  @time solve(obj)
end

```

Then the time reported is back to

```julia-repl
0.000003 seconds (1 allocation: 144 bytes)

```

All in a new session, and without removing any methods of `solve`.  
This suggests something about module scope is making `obj` more dynamic than one might expect. Without `const`, a type assertion (i.e. “typed global” syntax) or forcing it to be a local variable, the behaviour we observe is very similar to what happens when referencing global variables.
