Join GitHub today
GitHub is home to over 36 million developers working together to host and review code, manage projects, and build software together.Sign up
1.5x faster `ActiveRecord#respond_to?` - No longer allocates strings #34197
This is an alternative to #34195
The active record
The only time we need to allocate a string in this method is if the column does exist in the database, and since these are a limited number of strings (since column names are a finite set) then we can pre-generate all of them and use the same string.
We generate a list hash of column names and convert them to symbols, and store the value as the string name. This allows us to both check if the "name" exists as a column, but also provides us with a string object we can use for the
I then ran the test suite and found there was only one case where we're intentionally passing in a string and changed it to a symbol. (However there are tests where we are using a string key, but they don't ship with rails).
As re-written this method should never allocate unless the user passes in a string key, which is fairly uncommon with
This also eliminates the need to special case every common item that might come through the method via the
As a bonus this reduces 6,300 comparisons (in the CodeTriage app homepage) to 450 as we also no longer need to loop through the column array to check for an
On CodeTriage i'm seeing around a ~1% performance gain with this patch versus master. Here are the raw numbers https://gist.github.com/schneems/dc139542331baa9cca693ec7ecdfc40a.
2 times, most recently
Oct 11, 2018
referenced this pull request
Oct 12, 2018
I think that the idea here is good, but I wonder if its possible to move this conversion into a more generic method like
It also looks like the
Thanks for the review, you've got a sharp eye.
I checked and that code path does not get called by my benchmark with CodeTriage. When I limit to only the
1040 bytes is 0.13 % of a request (i.e. a little more than a tenth of one percent which is really tiny). Only 600 bytes are coming from
Changing attribute models to be symbol keyed would be an extremely difficult and large change, and for my case here it would only save an additional 600 bytes. It might be a worthwile change for another app or another page, but it's not a bottleneck for my benchmark. It was worth investigating but I think this is the approach I would like to move forward with for now.
@schneems I have worked out why you are not seeing the allocations in the
developer = Developer.select("id").first developer.respond_to?(:updated_at) #=> false developer.updated_at #=> ActiveModel::MissingAttributeError: missing attribute: updated_at developer.respond_to?(:updated_at_before_type_cast) #=> true developer.updated_at_before_type_cast #=> nil developer.respond_to?(:updated_at?) #=> true developer.updated_at? #=> ActiveModel::MissingAttributeError: missing attribute: updated_at
This behaviour is inconsistent and I think we should try to make consistent behaviour between them.
I think that I would prefer to make
I would be interested what the use cases are where fields are omitted from the select clause - I would think that in would normally be as a performance optimisation - in which case you wouldn't expect the behaviour if the application to change.
In the particular case of
That example is a major WAT. Good catch.
I think we should open a new issue and work on that behavior independently of this perf refactor. My goal in this PR is to change as little as possible while making things faster. This sounds like a bug that we need to make a decision on no matter what. I don't have the context here to know if the existing behavior is intentional or not.
The behavior of
Yes, that is my belief as well. I've done this as a perf optimization across CodeTriage. When I did this I basically limited to one or two fields and then loaded the page and then waited for errors to show which fields I was using but weren't being loaded.
I agree, that behavior is pretty confusing otherwise and would be hard to diagnose.
Oct 15, 2018
Oct 17, 2018
added a commit
this pull request
Nov 15, 2018
Here's a blog post about the how the code before this patch caused a hotspot in an app https://medium.com/appaloosa-store-engineering/ruby-memory-activerecord-and-draper-64f06abeeb34
added a commit
this pull request
Jan 8, 2019
On a microbenchmark, this makes the method call 1.5x faster https://gist.github.com/schneems/de03da00e1dd9f51b64e943878364b1b