Overloading `Base.getproperty` performance

Inspired by this post I wanted to add extra properties to my type, but noted a significant performance hit:

julia> using BenchmarkTools

julia> struct AType{T}
            data::T
       end

julia> struct BType{T}
           data::T
       end

julia> _getproperty(x::AType, ::Val{s}) where {s} = getfield(x, s)
julia> _getproperty(x::AType, ::Val{:two}) = 2*x.data
julia> Base.getproperty(x::AType, s::Symbol) = _getproperty(x, Val{s}())

julia> a = AType(1.0)
julia> b = BType(1.0)

julia> @benchmark b.data
BenchmarkTools.Trial: 
  memory estimate:  48 bytes
  allocs estimate:  3
  --------------
  minimum time:     69.511 ns (0.00% GC)
  median time:      73.613 ns (0.00% GC)
  mean time:        78.808 ns (2.21% GC)
  maximum time:     1.978 μs (95.25% GC)
  --------------
  samples:          10000
  evals/sample:     977

julia> @benchmark a.data
BenchmarkTools.Trial: 
  memory estimate:  48 bytes
  allocs estimate:  3
  --------------
  minimum time:     5.675 μs (0.00% GC)
  median time:      5.855 μs (0.00% GC)
  mean time:        5.909 μs (0.00% GC)
  maximum time:     13.520 μs (0.00% GC)
  --------------
  samples:          10000
  evals/sample:     6

As you can see, adding a custom getproperty(x::AType, ...) function makes accessing all properties (not only .two) much (almost 100x) slower. Are there other, more clever ways to overload getproperty, or will checking the value of s cost me performance, no matter what?

global scope?

@btime $b.data?

5 Likes

Yes, thats it. I was confused for a second, because I thought this would extrapolate the whole b.data into the expression, making it efficiently constant. But putting a small wrapper function around it all I got the same, faster times.