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 (#36)

There's a known issue with dynamic dispatch (Thanks to @topolarity 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! :)

Before:
```julia
julia> @Btime ((a)->a[1].y)($(Any[foo]))
  115.105 ns (2 allocations: 48 bytes)
Blob{Float32}(Ptr{Nothing} @0x000000016fe73d90, 8, 12)
```

After:
```julia
julia> @Btime ((a)->a[1].y)($(Any[foo]))
  48.015 ns (1 allocation: 32 bytes)
Blob{Float32}(Ptr{Nothing} @0x000000016fe73d90, 8, 12)

# Comparison to getproperty on a normal struct:
julia> @Btime ((a)->a[1].y)($(Any[Foo(2,3)]))
  52.550 ns (2 allocations: 48 bytes)
3.0f0
```

Follow up to #35.
  • Loading branch information
NHDaly authored Jan 24, 2025
1 parent 6668537 commit eb8d46d
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 @@ -169,7 +177,7 @@ end
end
i = fieldidx_lookup[field]
FT = fieldtype(T, i)
Blob{FT}(blob + blob_offset(T, i))
make_blob(FT, blob, blob_offset(T, i))
end
@noinline _throw_missing_field_error(T, field) = error("$T has no field $field")

Expand All @@ -180,7 +188,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

6 comments on commit eb8d46d

@NHDaly
Copy link
Member Author

@NHDaly NHDaly commented on eb8d46d Jan 24, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@JuliaRegistrator register()

Release Notes

Performance improvements for the case of dynamic, type-unstable field access.
Hopefully you don't do those, but if you do, this will be faster. :)

@JuliaRegistrator
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Registration pull request created: JuliaRegistries/General/123679

Tip: Release Notes

Did you know you can add release notes too? Just add markdown formatted text underneath the comment after the text
"Release notes:" and it will be added to the registry PR, and if TagBot is installed it will also be added to the
release that TagBot creates. i.e.

@JuliaRegistrator register

Release notes:

## Breaking changes

- blah

To add them here just re-invoke and the PR will be updated.

Tagging

After the above pull request is merged, it is recommended that a tag is created on this repository for the registered package version.

This will be done automatically if the Julia TagBot GitHub Action is installed, or can be done manually through the github interface, or via:

git tag -a v1.1.1 -m "<description of version>" eb8d46dffc6e72376b54d7d97a26597275a2efed
git push origin v1.1.1

Also, note the warning: Version 1.1.1 skips over 1.0.0
This can be safely ignored. However, if you want to fix this you can do so. Call register() again after making the fix. This will update the Pull request.

@NHDaly
Copy link
Member Author

@NHDaly NHDaly commented on eb8d46d Jan 24, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@JuliaRegistrator register()

Release Notes

Performance improvements for the case of dynamic, type-unstable field access.
Hopefully you don't do those, but if you do, this will be faster. :)

@JuliaRegistrator
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Registration pull request updated: JuliaRegistries/General/123679

Tip: Release Notes

Did you know you can add release notes too? Just add markdown formatted text underneath the comment after the text
"Release notes:" and it will be added to the registry PR, and if TagBot is installed it will also be added to the
release that TagBot creates. i.e.

@JuliaRegistrator register

Release notes:

## Breaking changes

- blah

To add them here just re-invoke and the PR will be updated.

Tagging

After the above pull request is merged, it is recommended that a tag is created on this repository for the registered package version.

This will be done automatically if the Julia TagBot GitHub Action is installed, or can be done manually through the github interface, or via:

git tag -a v1.1.1 -m "<description of version>" eb8d46dffc6e72376b54d7d97a26597275a2efed
git push origin v1.1.1

Also, note the warning: Version 1.1.1 skips over 1.0.0
This can be safely ignored. However, if you want to fix this you can do so. Call register() again after making the fix. This will update the Pull request.

@NHDaly
Copy link
Member Author

@NHDaly NHDaly commented on eb8d46d Jan 25, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@JuliaRegistrator register()

Release Notes

Performance improvements for the case of dynamic, type-unstable field access.
Hopefully you don't do those, but if you do, this will be faster. :)

@JuliaRegistrator
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Registration pull request updated: JuliaRegistries/General/123679

Tip: Release Notes

Did you know you can add release notes too? Just add markdown formatted text underneath the comment after the text
"Release notes:" and it will be added to the registry PR, and if TagBot is installed it will also be added to the
release that TagBot creates. i.e.

@JuliaRegistrator register

Release notes:

## Breaking changes

- blah

To add them here just re-invoke and the PR will be updated.

Tagging

After the above pull request is merged, it is recommended that a tag is created on this repository for the registered package version.

This will be done automatically if the Julia TagBot GitHub Action is installed, or can be done manually through the github interface, or via:

git tag -a v1.1.1 -m "<description of version>" eb8d46dffc6e72376b54d7d97a26597275a2efed
git push origin v1.1.1

Please sign in to comment.