Skip to content

Commit

Permalink
Another big improvement to dynamic property access: Fix dynamic dispa…
Browse files Browse the repository at this point in the history
…tch perf

There's a known issue with dynamic dispatch (Thanks to Cody to pointing
this out) where it's super slow if you dispatch on a type / constructor.

Our code had a dispatch on Blob{FT} with an unknown FT in the case of a
dynamic field access. That was causing very slow performance.

By changing this to a helper function (make_blob), we do a performant
dispatch, dramatically improving perf.

Now, dynamic field access on a Blob has the ~same~ even better
performance as dynamic field access on a regular julia object! :)

```julia
julia> @Btime getproperty($(foo), $(:y))
  19.057 ns (0 allocations: 0 bytes)
Blob{Float32}(Ptr{Nothing} @0x000000016fe73d90, 8, 12)

julia> @Btime getproperty($(Foo(1,2)), $(:y))
  25.477 ns (2 allocations: 48 bytes)
2.0f0
```
  • Loading branch information
NHDaly committed Jan 22, 2025
1 parent f813461 commit c3d80e0
Showing 1 changed file with 11 additions and 2 deletions.
13 changes: 11 additions & 2 deletions src/blob.jl
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,14 @@ struct Blob{T}
end
end

# OPTIMIZATION: Annoyingly julia dispatches on a *Type Constructor* are very expensive.
# So if you ever have a type unstable field access on a Blob, we will not know the type of
# the child Blob we will return, meaning a dynamic dispatch. Dispatching on Blob{FT}(...)
# for an unknown FT is very expensive. So instead, we dispatch to this function, which will
# be cheap because it has only one method. Then this function calls the constructor.
# This is a silly hack. :')
make_blob(::Type{BT}, b::Blob, offset) where {BT} = Blob{BT}(b + offset)

function Blob(ref::Base.RefValue{T}) where T
Blob{T}(pointer_from_objref(ref), 0, sizeof(T))
end
Expand Down Expand Up @@ -160,7 +168,7 @@ end
end
i = fieldindexes(T)[field]
FT = fieldtype(T, i)
Blob{FT}(blob + blob_offset(T, i))
make_blob(FT, blob, blob_offset(T, i))
end

@noinline function _throw_getindex_boundserror(blob::Blob, i::Int)
Expand All @@ -170,7 +178,8 @@ end
@boundscheck if i < 1 || i > fieldcount(T)
_throw_getindex_boundserror(blob, i)
end
return Blob{fieldtype(T, i)}(blob + Blobs.blob_offset(T, i))
FT = fieldtype(T, i)
return make_blob(FT, blob, Blobs.blob_offset(T, i))
end

Base.@propagate_inbounds function Base.setindex!(blob::Blob{T}, value::T) where T
Expand Down

0 comments on commit c3d80e0

Please sign in to comment.