Hello everyone, I am currently trying to code an Optical Ray Tracing package in Julia, and while trying to optimise the code for performance I ran into some issues that the function trace
that performs the tracing does a lot of allocations for calling the function intersection_distance
that has return type Float64.
Hereβs part of the trace function in question:
function trace(sys::OpticalSystem{N}) where {N}
traceable_rays::Vector{Rays{N}} = rays(sys)
system_surfaces::Vector{Surface{N}} = surfaces(sys)
for ray in traceable_rays
min_distance = Inf
min_index = 0
for (index, surface) in enumerate(system_surfaces)
distance = intersection_distance(surface, ray)
if distance < min_distance
min_distance = distance
min_index = index
end
end
end
end
I checked the intersection_distance
return types using the Base.return_types
method and the output is as follows:
julia> Base.return_types(intersection_distance)
4-element Vector{Any}:
Float64
Float64
Float64
Float64
The intersection_distance
function takes two parameters. The first parameter is of type <:Surface{N} and the second parameter is of type ::Ray{N}. However, when running @code_warntype
on the trace
function define above I get that the return type of the intersection_distance function is type Any
, which is the reason for doing so many allocations.
Hereβs the output of @code_warntype
:
MethodInstance for trace(::OpticalSystem{2})
from trace(sys::OpticalSystem{N}) where N @ Main REPL[7]:1
Static Parameters
N = 2
Arguments
#self#::Core.Const(trace)
sys::OpticalSystem{2}
Locals
@_3::Union{Nothing, Tuple{Any, Int64}}
system_surfaces::Vector{Surface{2}}
traceable_rays::Vector
@_6::Union{Nothing, Tuple{Tuple{Int64, Surface{2}}, Tuple{Int64, Int64}}}
ray::Any
min_index::Int64
min_distance::Any
@_10::Int64
surface::Surface{2}
index::Int64
distance::Any
@_14::Vector
@_15::Vector{Surface{2}}
Body::Nothing
1 ββ Core.NewvarNode(:(@_3))
β Core.NewvarNode(:(system_surfaces))
β Core.NewvarNode(:(traceable_rays))
β %4 = Main.rays(sys)::Vector{Ray{2}}
β %5 = Main.Vector::Core.Const(Vector)
β %6 = Core.apply_type(Main.Rays, $(Expr(:static_parameter, 1)))::Any
β %7 = Core.apply_type(%5, %6)::Type{Vector{_A}} where _A
β (@_14 = %4)
β %9 = (@_14::Vector{Ray{2}} isa %7)::Bool
ββββ goto #3 if not %9
2 ββ goto #4
3 ββ %12 = Base.convert(%7, @_14::Vector{Ray{2}})::Vector
ββββ (@_14 = Core.typeassert(%12, %7))
4 ββ (traceable_rays = @_14)
β %15 = Main.surfaces(sys)::Vector{Surface{2}}
β %16 = Main.Vector::Core.Const(Vector)
β %17 = Core.apply_type(Main.Surface, $(Expr(:static_parameter, 1)))::Core.Const(Surface{2})
β %18 = Core.apply_type(%16, %17)::Core.Const(Vector{Surface{2}})
β (@_15 = %15)
β %20 = (@_15 isa %18)::Core.Const(true)
ββββ goto #6 if not %20
5 ββ goto #7
6 ββ Core.Const(:(Base.convert(%18, @_15)))
ββββ Core.Const(:(@_15 = Core.typeassert(%23, %18)))
7 ββ (system_surfaces = @_15)
β %26 = traceable_rays::Vector
β (@_3 = Base.iterate(%26))
β %28 = (@_3 === nothing)::Bool
β %29 = Base.not_int(%28)::Bool
ββββ goto #15 if not %29
8 ββ %31 = @_3::Tuple{Any, Int64}
β (ray = Core.getfield(%31, 1))
β %33 = Core.getfield(%31, 2)::Int64
β (min_distance = Main.Inf)
β (min_index = 0)
β %36 = Main.enumerate(system_surfaces)::Base.Iterators.Enumerate{Vector{Surface{2}}}
β (@_6 = Base.iterate(%36))
β %38 = (@_6 === nothing)::Bool
β %39 = Base.not_int(%38)::Bool
ββββ goto #13 if not %39
9 ββ %41 = @_6::Tuple{Tuple{Int64, Surface{2}}, Tuple{Int64, Int64}}
β %42 = Core.getfield(%41, 1)::Tuple{Int64, Surface{2}}
β %43 = Base.indexed_iterate(%42, 1)::Core.PartialStruct(Tuple{Int64, Int64}, Any[Int64, Core.Const(2)])
β (index = Core.getfield(%43, 1))
β (@_10 = Core.getfield(%43, 2))
β %46 = Base.indexed_iterate(%42, 2, @_10::Core.Const(2))::Core.PartialStruct(Tuple{Surface{2}, Int64}, Any[Surface{2}, Core.Const(3)])
β (surface = Core.getfield(%46, 1))
β %48 = Core.getfield(%41, 2)::Tuple{Int64, Int64}
β (distance = Main.intersection_distance(surface, ray))
β %50 = (distance < min_distance)::Any
ββββ goto #11 if not %50
10 β (min_distance = distance)
ββββ (min_index = index)
11 β (@_6 = Base.iterate(%36, %48))
β %55 = (@_6 === nothing)::Bool
β %56 = Base.not_int(%55)::Bool
ββββ goto #13 if not %56
12 β goto #9
13 β (@_3 = Base.iterate(%26, %33))
β %60 = (@_3 === nothing)::Bool
β %61 = Base.not_int(%60)::Bool
ββββ goto #15 if not %61
14 β goto #8
15 β return nothing
You can clearly see that the distance
variable and min_distance
variable have only been inferred to type Any
although as far as I understand should be inferred to Float64
since the intersection_distance
function only returns type Float64
. I would be very thankful for any help in regards to how I can help Julia infer the correct return type for the intersection_distance
function.