Skip to content

Make ActiveSupport frozen-string-literal friendly.#29506

Merged
matthewd merged 5 commits intorails:masterfrom
pat:frozen-string-literals
Jul 1, 2017
Merged

Make ActiveSupport frozen-string-literal friendly.#29506
matthewd merged 5 commits intorails:masterfrom
pat:frozen-string-literals

Conversation

@pat
Copy link
Copy Markdown
Contributor

@pat pat commented Jun 20, 2017

As I'm sure many folks are aware, MRI 2.3 added the ability to have all string literals frozen by default, which has performance/memory usage benefits. It will become the default behaviour in MRI 3.0, though that is quite some time away!

This PR covers what's needed to make ActiveSupport play nicely when this feature is enabled. The test suite as it currently stands will continue to pass, there are no behaviour changes. Yes, it's for ActiveSupport only, but I figure it's a good place to start.

If this change is appreciated, then I can work further on the rest of the Rails gems to ensure they're working with frozen string literals as well. And if you want to have Travis run the tests with checks for this behaviour, then using pragmater and a test script can help with that (such as what I've done in my own gems and suggested for others).

Running the tests with frozen string literals enabled

Of course, if you want to run the test suite with frozen string literals enabled, you'll need the following locally:

Beyond that, all external dependencies (at least, to the extent they’re used by ActiveSupport) play happily with frozen string literals enabled by default in MRI 2.4 (which can be done so by the RUBYOPT environment variable: RUBYOPT="--enable-frozen-string-literal").

@tenderlove
Copy link
Copy Markdown
Member

I think it's good if we make Rails internals "frozen string" friendly. Though I'd prefer if we used .dup instead of String.new. I feel like I'm having Java flashbacks. 😉

@pat
Copy link
Copy Markdown
Contributor Author

pat commented Jun 20, 2017

Fair enough :) I meant to comment on the different approaches (String.new vs dup vs StringIO vs += in place of <<). I'll switch my changes to dup.

The ActiveSupport test suite only passes currently if it uses the latest unreleased commits for dalli, and a patch for Builder:
rails/builder#6

Beyond that, all external dependencies (at least, to the extent they’re used by ActiveSupport) are happy, including Nokogiri as of 1.8.0.
@pat pat force-pushed the frozen-string-literals branch from 8859757 to 036bdee Compare June 20, 2017 08:05
Includes two external changes because they're referenced within the ActiveModel test suite.
@koic
Copy link
Copy Markdown
Contributor

koic commented Jun 20, 2017

Incidentally, the encoding of the empty string generated by String.new without argument is not UTF-8 but ASCII-8BIT.

String.new.encoding     # => #<Encoding:ASCII-8BIT>
String.new("").encoding # => #<Encoding:UTF-8>
"".dup.encoding         # => #<Encoding:UTF-8>
"".encoding             # => #<Encoding:UTF-8>

I also think that it is preferable to use the String#dup rather than String.new so that this difference does not cause unexpected problems.
(There is no problem because String.new without argument did not exist in this PR.)

@pat
Copy link
Copy Markdown
Contributor Author

pat commented Jun 20, 2017

Thanks for that note @koic, good to know :)

I've gone ahead and made changes to ActiveModel, ActiveRecord, ActionMailer and ActionView. ActionJob didn't need any changes. For ActionCable, ActionPack and Railties, there are underlying dependencies that need patching (redis, rack, and thor, respectively), so I'll look into those tomorrow.

If anyone with particular focus/expertise on the libraries I have patched would like to review, please go ahead!

@pat
Copy link
Copy Markdown
Contributor Author

pat commented Jun 20, 2017

Also, @tenderlove: I updated my Builder patch to use dup as well 👍

@rafaelfranca
Copy link
Copy Markdown
Member

Can we add the magic comment to the files that were fixed?

@pat
Copy link
Copy Markdown
Contributor Author

pat commented Jun 20, 2017

@rafaelfranca we could, but that's not going to help stop bad uses of frozen string literals appear in all the other files. We could add the comment to all current files, but still, any new files that get added to Rails would need to have it as well - which may be acceptable?

Using either pragmater or RUBYOPT helps avoid regressions more reliably, I feel. Certainly, if all dependencies are frozen-string-literal friendly, then RUBYOPT is the simpler approach, but I'm a little wary of that given the number of dependencies Rails (and its test suite) relies upon.

@matthewd
Copy link
Copy Markdown
Member

@pat thanks for this!

I think the comment is important for these files in particular because otherwise "".dup will allocate two strings when the global option isn't set. Even so, we should consider whether any of these are particularly hot -- the magic comment can't help us for 2.2.

@rafaelfranca perhaps we should consider just adding the comment everywhere, and enabling the relevant rubocop rule. Once we (read: @pat 😉) have done the work to find all the strings that need a dup, the rest are all neutral at best, or otherwise a free [trivial] performance gain.

@matthewd
Copy link
Copy Markdown
Member

Bonus thought: even with the magic comment in every file, that wouldn't (AFAIK) cover eval calls, of which we have a few -- though most won't contain strings.

Probably narrow enough that we can ignore enforcement for now, until we can go full RUBYOPT on our test suite.

@tenderlove
Copy link
Copy Markdown
Member

@rafaelfranca perhaps we should consider just adding the comment everywhere, and enabling the relevant rubocop rule. Once we (read: @pat 😉) have done the work to find all the strings that need a dup, the rest are all neutral at best, or otherwise a free [trivial] performance gain.

+1. On that note, are there actual benchmarks that show the frozen string default helping (especially for a real app)? Last thing I saw was a modest speed increase for micro benchmarks. But the last tests I saw were probably 2 years ago when we were discussing the feature on ruby-core.

I still think we should move forward with frozen string literals (because I like them), I'm just genuinely curious about real performance.

@rafaelfranca
Copy link
Copy Markdown
Member

rafaelfranca commented Jun 21, 2017

perhaps we should consider just adding the comment everywhere, and enabling the relevant rubocop rule

That is the idea. I did that already in arel.

@matthewd matthewd merged commit 3d453b4 into rails:master Jul 1, 2017
matthewd added a commit that referenced this pull request Jul 1, 2017
Make ActiveSupport frozen-string-literal friendly.
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.

5 participants