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

Call to_a on cached ObjectSpace enumerator #2073

Merged
merged 1 commit into from
Nov 18, 2024

Conversation

warhammerkid
Copy link
Contributor

Based on the existence of the @@requested_contants code path, it seems safe for this method to return an Array in some cases, so simply caching the array of all Modules rather than the enumerator should be safe. Also, I have included the recommended optimization to derive all_classes from all_modules, which both simplifies things (no need for the @@requested_constants code path in both methods), as well as improves performance by skipping a full ObjectSpace traversal again.

Motivation

Closes #1974

Tests

This is simply an optimization to an existing tested behavior.

@warhammerkid warhammerkid requested a review from a team as a code owner November 14, 2024 19:49
@egiurleo egiurleo self-requested a review November 14, 2024 19:51
@warhammerkid
Copy link
Contributor Author

I have signed the CLA!

@egiurleo egiurleo requested a review from amomchilov November 14, 2024 19:59
@amomchilov amomchilov changed the title Call to_a on cached object space enumerator Call to_a on cached ObjectSpace enumerator Nov 14, 2024
Copy link
Contributor

@amomchilov amomchilov left a comment

Choose a reason for hiding this comment

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

Thanks for your contribution! I have 2 small suggestions, but otherwise LGTM!

Once you ship this, I'll do a quick benchmark against our monolith. I'm expecting there should be pretty substantial gains!

ObjectSpace.each_object(Class)
end,
T.cast(
all_modules.select { |k| k.is_a?(Class) },
Copy link
Contributor

Choose a reason for hiding this comment

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

I know it's kind of niche, but .grep(Class) is noticeably faster (last I benchmarked it), since it calls Class#=== directly, without needing the overhead of a block call like select needs

Suggested change
all_modules.select { |k| k.is_a?(Class) },
all_modules.grep(Class),

Copy link
Contributor

Choose a reason for hiding this comment

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

I actually didn't know this! Good to know.

Copy link
Contributor

Choose a reason for hiding this comment

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

Good to know.

It's cursed knowledge, as Aaron would say

Copy link
Contributor

Choose a reason for hiding this comment

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

2 other nice benefits:

  1. It removes the need for this cast because Enumerable#grep has a special overload for metatypes.
  2. The k.is_a?(Class) was calling is_a? on k, which could be arbitrary user code that breaks things. Class === k would always be on a receiver we control, Class.

Copy link
Member

Choose a reason for hiding this comment

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

Yes, @amomchilov is spot on, especially around the use of ===. In this project, we prefer to use === over is_a? whenever it is possible, since the receiver of === is usually under our control, so the result can be more dependable.

@@ -89,7 +88,7 @@ def all_modules
if @@requested_constants.any?
@@requested_constants.select { |k| k.is_a?(Module) }
Copy link
Contributor

Choose a reason for hiding this comment

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

May as well change this too, to be consistent.

Suggested change
@@requested_constants.select { |k| k.is_a?(Module) }
@@requested_constants.grep(Module)

@amomchilov
Copy link
Contributor

amomchilov commented Nov 14, 2024

Tested on our monolith, works great. We'll merge once we sort our CI issues

@egiurleo
Copy link
Contributor

Rebased to get CI fixes!

@egiurleo egiurleo enabled auto-merge November 18, 2024 17:08
@egiurleo egiurleo merged commit 2663f04 into Shopify:main Nov 18, 2024
15 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

Successfully merging this pull request may close these issues.

all_modules/all_classes scan the object space every time
4 participants