Dealing with mixed-type arrays

To be explained.

using BenchmarkTools
abstract type Material end

struct Material1 <: Material
  m :: Float64
end

struct Material2 <: Material
  m :: Float64
end

struct HitPoint{T <: Material}
  p :: Float64
  r :: Float64
  m :: T
end

#hit(p::HitPoint) = p.r*p.p*p.m.m

# the key is to create specialized methods for every subtype:
for type in subtypes(Material)
  eval(:(hit(p::HitPoint{$type}) = p.r*p.p*p.m.m))
  eval(:((p::HitPoint{$type})() = p.r*p.p*p.m.m))
end

function hits_naive(hitpoints)
  s = 0.
  for p in hitpoints 
    s += hit(p)
  end
  s
end

function hits_splitting(hitpoints)
  s = 0.
  for p in hitpoints
    if p isa HitPoint{Material1}
      s += hit(p::HitPoint{Material1})
    elseif p isa HitPoint{Material2}
      s += hit(p::HitPoint{Material2})
    end
  end
  s
end

function hits_functors(hitpoints)
  s = 0.
  for p in hitpoints 
    s += p()
  end
  s
end


using BenchmarkTools

n = 1000
hitpoints = [ rand(Bool) ? HitPoint(rand(),rand(),Material1(rand())) : 
              HitPoint(rand(),rand(),Material2(rand())) for i in 1:n ]

println(" Mixed types: ")
print("naive:");@btime hits_naive($hitpoints)
print("split:");@btime hits_splitting($hitpoints)
print("funct:");@btime hits_functors($hitpoints)

hitpoints_single = HitPoint{Material1}[ HitPoint(rand(),rand(),Material1(rand())) for i in 1:n ]

println(" Single type: ")
print("naive:");@btime hits_naive($hitpoints_single)
print("split:");@btime hits_splitting($hitpoints_single)
print("funct:");@btime hits_functors($hitpoints_single)

Results:

julia> include("./splitting2.jl")
 Mixed types:
naive:  1.510 μs (0 allocations: 0 bytes)
split:  2.975 μs (0 allocations: 0 bytes)
funct:  1.266 μs (0 allocations: 0 bytes)
 Single type:
naive:  992.167 ns (0 allocations: 0 bytes)
split:  991.833 ns (0 allocations: 0 bytes)
funct:  991.833 ns (0 allocations: 0 bytes)
117.02622654899025