# Performance discrepancy for different ways of piping/composing functions for handling runtime dispatch

**URL:** <https://discourse.julialang.org/t/performance-discrepancy-for-different-ways-of-piping-composing-functions-for-handling-runtime-dispatch/129773>\
**Category:** Performance\
**Tags:** function, runtime-dispatch\
**Created:** [June 10, 2025, 3:07am UTC](https://discourse.julialang.org/t/performance-discrepancy-for-different-ways-of-piping-composing-functions-for-handling-runtime-dispatch/129773 "2025-06-10T03:07:54Z")\
**Posts on this page:** 1\
**Showing post:** 9

<div class="post-metadata">

**Author:** ![frankwswang](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/frankwswang/32/18561_2.png) [@frankwswang](https://discourse.julialang.org/u/frankwswang)\
**Post date:** [June 13, 2025, 4:01am UTC](https://discourse.julialang.org/t/performance-discrepancy-for-different-ways-of-piping-composing-functions-for-handling-runtime-dispatch/129773/9 "2025-06-13T04:01:48Z")

</div>

Thanks for the explanation!!

> [@Benny](#):
>
> Types tell code how to handle their instances, so making thousands of something to do the same thing really suggests 1 type with thousands of instances, and the other way around will have significant optimization difficulties.

I think this is a good mental model, and I think in a sense I was following it by defining the method `foo1(::Val{N}) where {N}`, since this means that I only want a single method defined for all intances of **one type** : `Val`— **it’s just that it is not a concrete but an abstract type.** Although one could argue “same type” only applies to concrete types, as that’s how instances are directly identified by the dispatch system (except for `Type` and `Function`, of course).

However, in the case where the “different type” instances are differentiated by their type parameters, as long as these parameters contain enough type information about themselves useful for when they are treated as input values to a function, we can still have type-stable code:

```julia
julia> foo3(::Val{<:Integer}) = 1
foo3 (generic function with 1 method)

julia> foo3(::Val{<:AbstractFloat}) = 2
foo3 (generic function with 2 methods)

julia> typePool = (Float16, Float32, Float64, Int8, Int16, Int32, Int64)
(Float16, Float32, Float64, Int8, Int16, Int32, Int64)

julia> b = Val.(getindex.(Ref(typePool), rand(1:7, 1000)));

julia> @code_warntype foo3.(b)
MethodInstance for (::var"##dotfunction#231#2")(::Vector{Val})
  from (::var"##dotfunction#231#2")(x1) @ Main none:0
Arguments
  #self#::Core.Const(var"##dotfunction#231#2"())
  x1::Vector{Val}
Body::Vector{Int64}
1 ─ %1 = Base.broadcasted(Main.foo3, x1)::Base.Broadcast.Broadcasted{Base.Broadcast.DefaultArrayStyle{1}, Nothing, typeof(foo3), Tuple{Vector{Val}}}
│ %2 = Base.materialize(%1)::Vector{Int64}
└── return %2

```

On the contrary, for non-`Type` parameters like `N` that do not (and currently cannot) carry any type information about themselves, whenever a function directly manipulates these parameters, the code becomes type unstable:

```julia
julia> @code_warntype foo1.(a)
MethodInstance for (::var"##dotfunction#240#1")(::Vector{Val})
  from (::var"##dotfunction#240#1")(x1) @ Main none:0
Arguments
  #self#::Core.Const(var"##dotfunction#240#1"())
  x1::Vector{Val}
Body::AbstractVector
1 ─ %1 = Base.broadcasted(Main.foo1, x1)::Base.Broadcast.Broadcasted{Base.Broadcast.DefaultArrayStyle{1}, Nothing, typeof(foo1), Tuple{Vector{Val}}}
│ %2 = Base.materialize(%1)::AbstractVector
└── return %2

```

This is why I think not having complete type annotation capability for the signature of parametric types can also lead to avoidable performance overhead.

---

_[View the full topic](https://discourse.julialang.org/t/performance-discrepancy-for-different-ways-of-piping-composing-functions-for-handling-runtime-dispatch/129773)._
