Allocation and slow down when # of types involved increase (slower than C++ virtual methods)

@rdeits unfortunately rdeits/ConcreteInterfaces.jl is not working with a recent version of Julia. In any case, I have made a test with FunctionWrapper by hand instead of using the @interface macro, which looks very handy.

I have defined a partial interface to Shapes with only two methods (inside and extent) to start with.

#---Define shape interfaces  (aka C++ virtual table)------------------------------------------------
struct ShapeInterface{T<:AbstractFloat} <: AbstractInterface
    self::AbstractShape{T}
    inside::FunctionWrapper{Int64, Tuple{Point3{T}}}
    extent::FunctionWrapper{Tuple{Point3{T},Point3{T}},Tuple{}}
    ShapeInterface{T}(self::AbstractShape{T}) where T = new(self, (p::Point3{T} -> inside(self, p)), (()->extent(self)))
end
import Geom4hep: inside, extent
inside(i::ShapeInterface{T}, p::Point3{T}) = i.inside(p)
extent(i::ShapeInterface{T}) = i.extent()

then I define the new BooleanShape like this:

struct NBooleanShape{T<:AbstractFloat, OP} <: AbstractShape{T}
    left::ShapeInterface{T}   # the mother (or left) volume A in unplaced form
    right::ShapeInterface{T}  # (or right) volume B in placed form, acting on A with a boolean operation
    transformation::Transformation3D{T}          # placement of "right" with respect of "left" 
end

The NBooleanShape has good properties (type stability, no allocations at runtime, etc.) but the performance is not better. With a nested boolean shape you get a factor 2 worst performance.

with the original code (as in the head of repository)
  62.709 ns (0 allocations: 0 bytes)
with the new BooleanShape
  110.988 ns (0 allocations: 0 bytes)
full example
using Revise
using Geom4hep

using FunctionWrappers
import FunctionWrappers: FunctionWrapper
abstract type AbstractInterface end

#---Define shape interfaces  (aka C++ virtual table)------------------------------------------------
struct ShapeInterface{T<:AbstractFloat} <: AbstractInterface
    self::AbstractShape{T}
    inside::FunctionWrapper{Int64, Tuple{Point3{T}}}
    extent::FunctionWrapper{Tuple{Point3{T},Point3{T}},Tuple{}}
    ShapeInterface{T}(self::AbstractShape{T}) where T = new(self, (p::Point3{T} -> inside(self, p)), (()->extent(self)))
end

import Geom4hep: inside, extent
inside(i::ShapeInterface{T}, p::Point3{T}) = i.inside(p)
extent(i::ShapeInterface{T}) = i.extent()


#---New definition of Boolean Shapes--------------------------------------------------------------------
struct NBooleanShape{T<:AbstractFloat, OP} <: AbstractShape{T}
    left::ShapeInterface{T}   # the mother (or left) volume A in unplaced form
    right::ShapeInterface{T}  # (or right) volume B in placed form, acting on A with a boolean operation
    transformation::Transformation3D{T}          # placement of "right" with respect of "left" 
end
const NBooleanUnion{T} = NBooleanShape{T, :union}
const NBooleanSubtraction{T} =  NBooleanShape{T, :subtraction}
const NBooleanIntersection{T} = NBooleanShape{T, :intersection}

#---Constructor--------------------------------------------------------------------------------------
function NBooleanSubtraction(left::AbstractShape{T}, right::AbstractShape{T}, place::Transformation3D{T}=one(Transformation3D{T})) where T<:AbstractFloat
    NBooleanSubtraction{T}(ShapeInterface{T}(left), ShapeInterface{T}(right), place)
end

function inside(shape::NBooleanSubtraction{T}, point::Point3{T})::Int64
    (; left, right, transformation) = shape
    lpoint = transformation * point
    positionA = inside(left, point)
    positionA == kOutside && return kOutside
    positionB = inside(right, lpoint)
    if positionA == kInside && positionB == kOutside
        return kInside;
    elseif positionA == kInside && positionB == kSurface ||
           positionB == kOutside && positionA == kSurface
        return kSurface
    else
        return kOutside
    end
end

function extent(shape::NBooleanSubtraction{T})::Tuple{Point3{T},Point3{T}} where {T}
    extent(shape.left)
end


const T = Float64
#---New boolean instance
nboolean = NBooleanSubtraction(NBooleanSubtraction(NBooleanSubtraction(NBooleanSubtraction(Box{T}(2.035, 100.35000000000001, 0.5750000000000001),
                                                                                      Box{T}(0.3175, 100.35000000000001, 0.065),
                                                                                      Transformation3D{T}(1.7175000000000002, 0.0, 0.51)),
                                                                   Box{T}(0.3175, 100.35000000000001, 0.065),
                                                                   Transformation3D{T}(1.7175000000000002, 0.0, -0.51)),
                                                Box{Float64}(0.3175, 100.35000000000001, 0.065),
                                                Transformation3D{T}(-1.7175000000000002, 0.0, 0.51)),
                            Box{Float64}(0.3175, 100.35000000000001, 0.065),
                            Transformation3D{T}(-1.7175000000000002, 0.0, -0.51))
#---Old boolean instance
boolean = BooleanSubtraction(BooleanSubtraction(BooleanSubtraction(BooleanSubtraction(Box{T}(2.035, 100.35000000000001, 0.5750000000000001),
                                                                                      Box{T}(0.3175, 100.35000000000001, 0.065),
                                                                                      Transformation3D{T}(1.7175000000000002, 0.0, 0.51)),
                                                                   Box{T}(0.3175, 100.35000000000001, 0.065),
                                                                   Transformation3D{T}(1.7175000000000002, 0.0, -0.51)),
                                                Box{Float64}(0.3175, 100.35000000000001, 0.065),
                                                Transformation3D{T}(-1.7175000000000002, 0.0, 0.51)),
                             Box{Float64}(0.3175, 100.35000000000001, 0.065),
                             Transformation3D{T}(-1.7175000000000002, 0.0, -0.51))

point = Point3{T}(0,0,0)

# benchmaking                             
using BenchmarkTools
@btime inside(boolean, point)
@btime inside(nboolean, point)
2 Likes