Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Use attached_object in ruby 3.2+. #318

Merged
merged 1 commit into from
Oct 23, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ _No breaking changes!_
- Updated `Dry::Core` gem version to 1.0.0 in benchmarks [[#297]](https://github.com/panorama-ed/memo_wise/pull/297)
- Updated `Memery` gem version to 1.5.0 in benchmarks [[#313]](https://github.com/panorama-ed/memo_wise/pull/313)
- Updated `Memoized` gem version to 1.1.1 in benchmarks [[#288]](https://github.com/panorama-ed/memo_wise/pull/288)
- In Ruby3.2+, for singleton classes, use `#attached_object` instead of `ObjectSpace` [[#318]](https://github.com/panorama-ed/memo_wise/pull/318)
- Reorganized `CHANGELOG.md` for improved clarity and completeness
[[#282](https://github.com/panorama-ed/memo_wise/pull/282)]

Expand Down
38 changes: 19 additions & 19 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -116,17 +116,17 @@ Benchmarks are run in GitHub Actions, and the tables below are updated with ever

Results using Ruby 3.2.2:

|Method arguments|`Dry::Core`\* (1.0.0)|`Memery` (1.5.0)|
|Method arguments|`Dry::Core`\* (1.0.1)|`Memery` (1.5.0)|
|--|--|--|
|`()` (none)|0.55x|3.69x|
|`(a)`|1.60x|7.84x|
|`(a, b)`|1.16x|5.52x|
|`(a:)`|1.48x|12.66x|
|`(a:, b:)`|1.10x|8.56x|
|`(a, b:)`|1.08x|8.68x|
|`(a, *args)`|0.79x|1.36x|
|`(a:, **kwargs)`|0.84x|2.16x|
|`(a, *args, b:, **kwargs)`|0.71x|1.37x|
|`()` (none)|0.54x|3.62x|
|`(a)`|1.54x|7.74x|
|`(a, b)`|1.29x|5.58x|
|`(a:)`|1.55x|12.37x|
|`(a:, b:)`|1.15x|8.76x|
|`(a, b:)`|1.15x|8.75x|
|`(a, *args)`|0.84x|1.54x|
|`(a:, **kwargs)`|0.79x|2.13x|
|`(a, *args, b:, **kwargs)`|0.69x|1.38x|

\* `Dry::Core`
[may cause incorrect behavior caused by hash collisions](https://github.com/dry-rb/dry-core/issues/63).
Expand All @@ -135,15 +135,15 @@ Results using Ruby 2.7.8 (because these gems raise errors in Ruby 3.x):

|Method arguments|`DDMemoize` (1.0.0)|`Memoist` (0.16.2)|`Memoized` (1.1.1)|`Memoizer` (1.0.3)|
|--|--|--|--|--|
|`()` (none)|22.61x|2.39x|26.68x|2.89x|
|`(a)`|21.17x|14.68x|23.11x|12.37x|
|`(a, b)`|16.49x|12.08x|17.26x|10.52x|
|`(a:)`|28.76x|22.96x|24.80x|21.01x|
|`(a:, b:)`|23.40x|19.54x|20.70x|17.90x|
|`(a, b:)`|22.58x|18.40x|19.51x|16.97x|
|`(a, *args)`|3.10x|2.26x|3.32x|1.98x|
|`(a:, **kwargs)`|2.77x|2.28x|2.51x|2.12x|
|`(a, *args, b:, **kwargs)`|2.14x|1.81x|1.96x|1.72x|
|`()` (none)|24.22x|2.42x|26.43x|2.70x|
|`(a)`|22.08x|15.19x|22.03x|13.67x|
|`(a, b)`|19.12x|14.01x|18.60x|12.80x|
|`(a:)`|30.21x|24.29x|26.43x|23.11x|
|`(a:, b:)`|27.73x|22.97x|25.11x|21.89x|
|`(a, b:)`|26.87x|22.76x|23.71x|21.26x|
|`(a, *args)`|3.15x|2.30x|3.18x|2.11x|
|`(a:, **kwargs)`|2.89x|2.40x|2.69x|2.28x|
|`(a, *args, b:, **kwargs)`|2.12x|1.82x|1.96x|1.74x|

You can run benchmarks yourself with:

Expand Down
50 changes: 38 additions & 12 deletions lib/memo_wise/internal_api.rb
Original file line number Diff line number Diff line change
Expand Up @@ -126,18 +126,7 @@ def self.key_str(method)
def self.original_class_from_singleton(klass)
raise ArgumentError, "Must be a singleton class: #{klass.inspect}" unless klass.singleton_class?

# Since we call this method a lot, we memoize the results. This can have a
# huge impact; for example, in our test suite this drops our test times
# from over five minutes to just a few seconds.
@original_class_from_singleton ||= {}

# Search ObjectSpace
# * 1:1 relationship of singleton class to original class is documented
# * Performance concern: searches all Class objects
# But, only runs at load time and results are memoized
@original_class_from_singleton[klass] ||= ObjectSpace.each_object(Module).find do |cls|
cls.singleton_class == klass
end
find_attached_object(klass)
end

# Convention we use for renaming the original method when we replace with
Expand Down Expand Up @@ -208,5 +197,42 @@ def self.target_class(target)
end
end
private_class_method :target_class

if Module.singleton_class.respond_to?(:attached_object)
# In Ruby3.2+, for singleton classes, `#attached_object` returns the object this class is for
# https://bugs.ruby-lang.org/issues/12084
#
# @param klass [Class]
# Singleton class to find the original class of
#
# @return Class
# Original class for which `klass` is the singleton class.
def self.find_attached_object(klass)
klass.attached_object
end
else
# :nocov:
# @param klass [Class]
# Singleton class to find the original class of
#
# @return Class
# Original class for which `klass` is the singleton class.
def self.find_attached_object(klass)
# Since we call this method a lot, we memoize the results. This can have a
# huge impact; for example, in our test suite this drops our test times
# from over five minutes to just a few seconds.
@original_class_from_singleton ||= {}

# Search ObjectSpace
# * 1:1 relationship of singleton class to original class is documented
# * Performance concern: searches all Class objects
# But, only runs at load time and results are memoized
@original_class_from_singleton[klass] ||= ObjectSpace.each_object(Module).find do |cls|
cls.singleton_class == klass
end
end
# :nocov:
end
private_class_method :find_attached_object
end
end