Skip to content

Remove frequently unnecessary hash allocation#11854

Merged
mitchellhenke merged 1 commit intomainfrom
mitchellhenke/remove-frequently-unneccessary-hash-allocation
Feb 7, 2025
Merged

Remove frequently unnecessary hash allocation#11854
mitchellhenke merged 1 commit intomainfrom
mitchellhenke/remove-frequently-unneccessary-hash-allocation

Conversation

@mitchellhenke
Copy link
Contributor

@mitchellhenke mitchellhenke commented Feb 7, 2025

🛠 Summary of changes

Sort of a continuation of #11841 and #11844, this PR removes a frequently unnecessary hash allocation. #fetch(key, default) will always instantiate the default even if the key exists. The behavior is not identical in that a nil value in the hash will be returned if it is there and this will fall through and return the empty hash. We should never be storing nil here as we use delete when clearing the session value.

We could alternatively use the block form #fetch(key) { |key| ... } as a "lazy" instantiation of the hash, but it didn't feel necessary in this case.

The pattern of a default empty hash value appears in other places in the codebase, but session is called quite frequently and seems to contribute much more significantly.

changelog: Internal, Performance, Remove fetch to avoid unnecessary hash allocation
@mitchellhenke mitchellhenke requested a review from a team February 7, 2025 18:46
@mitchellhenke mitchellhenke merged commit aa462e3 into main Feb 7, 2025
@mitchellhenke mitchellhenke deleted the mitchellhenke/remove-frequently-unneccessary-hash-allocation branch February 7, 2025 18:59

def session
user_session.fetch(:idv, {})
user_session[:idv] || {}
Copy link
Contributor

@aduth aduth Feb 7, 2025

Choose a reason for hiding this comment

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

As a further micro-optimization, I wonder if it could be useful to have a frozen empty hash constant reference somewhere we could use to further avoid allocations in these cases?

Like a...

EMPTY_HASH = {}.freeze

Poking around the Rails source, I did find a couple references to constants used for this purpose, though I dunno that we'd want to use them directly:

  • ActiveRecord::QueryMethods::FROZEN_EMPTY_HASH
  • ActionController::Parameters::EMPTY_HASH

Copy link
Contributor

Choose a reason for hiding this comment

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

Does the default get instantiated and persisted in the session? Like would we want something like this to preserve behavior?

Suggested change
user_session[:idv] || {}
user_session[:idv] ||= {}

Copy link
Contributor Author

Choose a reason for hiding this comment

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

As a further micro-optimization, I wonder if it could be useful to have a frozen empty hash constant reference somewhere we could use to further avoid allocations in these cases?

I assumed it was likely that the hash is mutated, so I'm less sure if a frozen constant would be possible here.

Does the default get instantiated and persisted in the session? Like would we want something like this to preserve behavior?

Not in the current implementation, the result of this is used in instantiation here. I think it's in part due to the class being kind of unique in that it uses method_missing to allow for calling idv_session.applicant = 'abc' to set values in the hash. I'd like to refactor that since the mutation of the method here also seems to be a bit of a hotspot, but that's a bigger effort.

zachmargolis added a commit that referenced this pull request Feb 7, 2025
- Per comment on #11854, the dynamic access and string manipulation
  makes these methods a hotspot

- Dynamically defining these methods one should hopefully let the
  JIT optimize this a little better, and lets us use the default
  implementations for #method_missing and #respond_to_missing?

changelog: Internal, Source code, Optimize dynamic method access
zachmargolis added a commit that referenced this pull request Feb 7, 2025
* Remove method_missing usage in Idv::Session

- Per comment on #11854, the dynamic access and string manipulation
  makes these methods a hotspot

- Dynamically defining these methods one should hopefully let the
  JIT optimize this a little better, and lets us use the default
  implementations for #method_missing and #respond_to_missing?

* Use explicit setter methods in specs

changelog: Internal, Source code, Optimize dynamic method access
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants