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
Allocation free Integer#to_s #27736
Allocation free Integer#to_s #27736
Conversation
Thanks for the pull request, and welcome! The Rails team is excited to review your changes, and you should hear from @pixeltrix (or someone else) soon. If any changes to this PR are deemed necessary, please add them as extra commits. This ensures that the reviewer can see what has changed since they last reviewed the code. Due to the way GitHub handles out-of-date commits, this should also make it reasonably obvious what issues have or haven't been addressed. Large or tricky changes may require several passes of review and changes. This repository is being automatically checked for code quality issues using Code Climate. You can see results for this analysis in the PR status below. Newly introduced issues should be fixed before a Pull Request is considered ready to review. Please see the contribution instructions for more information. |
I profiled a small benchmark: StackProf.run(mode: :object, out: '/tmp/stackprof-object-int-to_s-as-fix.dump') do
1_000.times do
42.to_s
end
end Pure ruby:
current active_support:
With this fix:
So it look like there is still an allocation I haven't removed. |
Turns out So I removed it:
It might break backwards compatibility, in case people used that module on custom classes. But I think it's fair to assume that module is a private API. |
6f8e298
to
22c5da5
Compare
private_constant :DEFAULT | ||
def to_s(format = DEFAULT, options = DEFAULT) | ||
return super() if format == DEFAULT | ||
return super(format) if format.is_a?(Integer) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
These can both be when
clauses, I think?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Good point.
format, options = args | ||
options ||= {} | ||
DEFAULT = Object.new | ||
private_constant :DEFAULT |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It's private, but we're still ending up adding this to core classes. Maybe worth storing it elsewhere?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Any idea where it could go?
22c5da5
to
ebccffa
Compare
Does this not work? def to_s(format = nil, options = nil)
case format
when nil
super()
when Integer, String
super(format)
when :phone
return ActiveSupport::NumberHelper.number_to_phone(self, options || {})
when :currency
return ActiveSupport::NumberHelper.number_to_currency(self, options || {})
when :percentage
return ActiveSupport::NumberHelper.number_to_percentage(self, options || {})
when :delimited
return ActiveSupport::NumberHelper.number_to_delimited(self, options || {})
when :rounded
return ActiveSupport::NumberHelper.number_to_rounded(self, options || {})
when :human
return ActiveSupport::NumberHelper.number_to_human(self, options || {})
when :human_size
return ActiveSupport::NumberHelper.number_to_human_size(self, options || {})
else
super()
end
end When format is nil we rely on the default behavior of the super method, when it's an integer then it's probably |
Definitely simpler. I stupidly went with I'll update the PR. |
ebccffa
to
713fb6f
Compare
Updated. |
Not happy with that final
vs.
However a bare |
Agreed. |
713fb6f
to
b9bda7f
Compare
@pixeltrix updated. Note that I had to add a case for Another slightly backward incompatible change is that before |
return ActiveSupport::NumberHelper.number_to_human_size(self, options) | ||
return ActiveSupport::NumberHelper.number_to_human_size(self, options || {}) | ||
when Symbol | ||
super() |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think this clause is still wrong - we should either delete it and it'll raise the appropriate error (TypeError
from Integer
or ArgumentError
from Float
), or we should raise an ArgumentError
with an unknown format message rather than just silently returning the default. That would be a better developer experience since it'd blow up if there was a typo in the format name.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I agree.. but IMO we should probably keep that change away from this perf one
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yep I agree as well.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
👍 performance change can be backported to 5-0-stable but change to raising can't.
Backported in 051288d @casperisfine thanks! 👍 |
Thanks to you! |
We were profiling in production, and were surprised to see
NumericWithFormat#to_s
account for 2% of global object allocations.Looking at the code, it turns out that even if you want to call the regular
Integer#to_s
(which is fairly common, especially if you display numbers in your views) you end up allocating both an Array (splat args) and a Hash (options).This PR prevent those allocations if you are not trying to call any advanced formatting.
I know 2 objects isn't much at all, and that this version is a bit more complex to grasp, but for such a hotspot I believe it's worth it.
@rafaelfranca @csfrancis @camilo