From: "byroot (Jean Boussier) via ruby-core" <ruby-core@ml.ruby-lang.org>
To: ruby-core@ml.ruby-lang.org
Cc: "byroot (Jean Boussier)" <noreply@ruby-lang.org>
Subject: [ruby-core:116449] [Ruby master Feature#20205] Enable `frozen_string_literal` by default
Date: Thu, 25 Jan 2024 16:49:54 +0000 (UTC) [thread overview]
Message-ID: <redmine.journal-106473.20240125164953.7941@ruby-lang.org> (raw)
In-Reply-To: <redmine.issue-20205.20240123152642.7941@ruby-lang.org>
Issue #20205 has been updated by byroot (Jean Boussier).
Alright, I finally got hold of the benchmarking server. For the record it's an AWS c5n.metal, with various tuning like disabling frequency scaling etc to hopefully get more stable results.
- `yjit-bench` revision: https://github.com/Shopify/yjit-bench/commit/95e1a3caddc7281fbaf5bd0f20b197561453993f
- `ruby` revision: https://github.com/Shopify/ruby/commit/bb0cee8daba4b70cedb40b36af05d796956475d6
Both rubyes are ran with YJIT enabled. `mutable` has `MUTABLE_STRINGS=1` which makes the `frozen_string_literal: true` comment become essentially a noop.
I ran headline benchmark twice for good measures, most of them seem to be consistent with 1% between the two runs, except for `ruby-lsp` which has widely inconsistent results (`+/-12%`??), and `hexapdf` too to some extent (`+/-4%`).
```
mutable: ruby 3.4.0dev (2024-01-24T08:24:16Z mutable-strings bb0cee8dab) +YJIT dev [x86_64-linux]
frozen: ruby 3.4.0dev (2024-01-24T08:24:16Z mutable-strings bb0cee8dab) +YJIT dev [x86_64-linux]
-------------- ------------ ---------- ----------- ---------- -------------- --------------
bench mutable (ms) stddev (%) frozen (ms) stddev (%) frozen 1st itr mutable/frozen
activerecord 72.8 1.2 70.8 1.2 1.03 1.03
chunky-png 1666.3 0.1 1672.7 0.2 1.00 1.00
erubi-rails 2330.1 0.3 2312.2 0.2 1.01 1.01
hexapdf 3960.1 8.9 3614.7 7.3 1.04 1.10
liquid-c 107.1 1.3 101.3 1.4 0.95 1.06
liquid-compile 100.0 2.8 98.5 3.1 1.00 1.02
liquid-render 153.0 1.1 137.8 1.1 0.97 1.11
lobsters 1346.6 8.5 1239.2 7.6 1.00 1.09
mail 202.8 0.8 198.9 1.0 1.02 1.02
psych-load 3723.2 0.1 3661.3 0.1 1.01 1.02
railsbench 2669.8 0.1 2519.6 0.2 1.02 1.06
rubocop 278.6 5.9 272.5 6.1 1.00 1.02
ruby-lsp 217.5 8.7 250.7 10.4 0.98 0.87
sequel 111.8 0.7 111.8 0.8 1.01 1.00
-------------- ------------ ---------- ----------- ---------- -------------- --------------
Legend:
- frozen 1st itr: ratio of mutable/frozen time for the first benchmarking iteration.
- mutable/frozen: ratio of mutable/frozen time. Higher is better for frozen. Above 1 represents a speedup.
mutable: ruby 3.4.0dev (2024-01-24T08:24:16Z mutable-strings bb0cee8dab) +YJIT dev [x86_64-linux]
frozen: ruby 3.4.0dev (2024-01-24T08:24:16Z mutable-strings bb0cee8dab) +YJIT dev [x86_64-linux]
-------------- ------------ ---------- ----------- ---------- -------------- --------------
bench mutable (ms) stddev (%) frozen (ms) stddev (%) frozen 1st itr mutable/frozen
activerecord 72.8 1.2 70.8 1.3 1.00 1.03
chunky-png 1664.1 0.2 1672.5 0.2 1.01 0.99
erubi-rails 2351.9 0.2 2311.4 0.2 1.01 1.02
hexapdf 3759.9 7.0 3696.5 5.8 1.04 1.02
liquid-c 108.0 1.1 101.3 1.4 0.97 1.07
liquid-compile 100.5 3.1 98.5 3.1 0.99 1.02
liquid-render 152.8 1.0 138.0 1.1 0.97 1.11
lobsters 1339.0 9.8 1244.2 7.9 1.00 1.08
mail 202.6 0.7 198.9 1.0 1.00 1.02
psych-load 3627.0 0.1 3659.6 0.0 0.99 0.99
railsbench 2670.7 0.1 2560.3 1.7 1.02 1.04
rubocop 279.5 6.3 271.4 6.1 1.00 1.03
ruby-lsp 274.2 9.5 245.0 9.8 0.98 1.12
sequel 111.9 0.7 112.0 1.3 1.01 1.00
-------------- ------------ ---------- ----------- ---------- -------------- --------------
Legend:
- frozen 1st itr: ratio of mutable/frozen time for the first benchmarking iteration.
- mutable/frozen: ratio of mutable/frozen time. Higher is better for frozen. Above 1 represents a speedup.
```
Overall the performance benefit seem much more important than on my initial benchmark, likely in part because YJIT is enabled and also likely in part because of the different architecture (`X86_64` vs `ARM64`).
----------------------------------------
Feature #20205: Enable `frozen_string_literal` by default
https://bugs.ruby-lang.org/issues/20205#change-106473
* Author: byroot (Jean Boussier)
* Status: Open
* Priority: Normal
----------------------------------------
### Context
The `frozen_string_literal: true` pragma was introduced in Ruby 2.3, and as far as I'm aware the plan was initially to make it the default for Ruby 3.0, but this plan was abandoned because it would be too much of a breaking change without any real further notice.
According to Matz, he still wishes to enable `frozen_string_literal` by default in the future, but a reasonable migration plan is required.
The main issue is backward compatibility, flipping the switch immediately would break a lot of code, so there must be some deprecation period.
The usual the path forward for this kind of change is to emit deprecation warnings one of multiple versions in advance.
One example of that was the Ruby 2.7 keyword argument deprecation. It was quite verbose, and some users were initially annoyed, but I think the community pulled through it and I don't seem to hear much about it anymore.
So for frozen string literals, the first step would be to start warning when a string that would be frozen in the future is mutated.
### Deprecation Warning Implementation
I implemented a quick proof of concept with @etienne in https://github.com/Shopify/ruby/pull/549
In short:
- Files with `# frozen_string_literal: true` or `# frozen_string_literal: false` don't change in behavior at all.
- Files with no `# frozen_string_literal` comment are compiled to use `putchilledstring` opcode instead of regular `putstring`.
- This opcode mark the string with a user flag, when these strings are mutated, a warning is issued.
Currently the proof of concept issue the warning at the mutation location, which in some case can make locating where the string was allocated a bit hard.
But it is possible to improve it so the message also include the location at which the literal string was allocated, and learning from the keyword argument warning experience,
we can record which warnings were already issued to avoid spamming users with duplicated warnings.
As currently implemented, there is almost no overhead. If we modify the implementation to record the literal location,
we'd incur a small memory overhead for each literal string in a file without an explicit `frozen_string_literal` pragma.
But I believe we could do it in a way that has no overhead if `Warning[:deprecated] = false`.
### Timeline
The migration would happen in 3 steps, each step can potentially last multiple releases. e.g. `R0` could be `3.4`, `R1` be `3.7` and `R2` be `4.0`.
I don't have a strong opinion on the pace.
- Release `R0`: introduce the deprecation warning (only if deprecation warnings enabled).
- Release `R1`: make the deprecation warning show up regardless of verbosity level.
- Release `R2`: make string literals frozen by default.
### Impact
Given that `rubocop` is quite popular in the community and it has enforced the usage of `# frozen_string_literal: true` for years now,
I suspect a large part of the actively maintained codebases in the wild wouldn't see any warnings.
And with recent versions of `minitest` enabling deprecation warnings by default (and [potentially RSpec too](https://github.com/rspec/rspec-core/issues/2867)),
the few that didn't migrate will likely be made compatible quickly.
The real problem of course are the less actively developed libraries and applications. For such cases, any codebase can remain compatible by setting `RUBYOPT="--disable=frozen_string_literal"`,
and so even after `R2` release. The flag would never be removed any legacy codebase can continue upgrading Ruby without changing a single line of cod by just flipping this flag.
### Workflow for library maintainers
As a library maintainer, fixing the deprecation warnings can be as simple as prepending `# frozen_string_literal: false` at the top of all their source files, and this will keep working forever.
Alternatively they can of course make their code compatible with frozen string literals.
Code that is frozen string literal compatible doesn't need to explicitly declare it. Only code that need it turned of need to do so.
### Workflow for application owners
For application owners, the workflow is the same than for libraries.
However if they depend on a gem that hasn't updated, or that they can't upgrade it, they can run their application with `RUBYOPT="--disable=frozen_string_literal"` and it will keep working forever.
Any user running into an incompatibility issue can set `RUBYOPT="--disable=frozen_string_literal"` forever, even in `4.x`, the only thing changing is the default value.
And any application for which all dependencies have been made fully frozen string literal compatible can set `RUBYOPT="--enable=frozen_string_literal"` and start immediately removing magic comment from their codebase.
--
https://bugs.ruby-lang.org/
______________________________________________
ruby-core mailing list -- ruby-core@ml.ruby-lang.org
To unsubscribe send an email to ruby-core-leave@ml.ruby-lang.org
ruby-core info -- https://ml.ruby-lang.org/mailman3/postorius/lists/ruby-core.ml.ruby-lang.org/
next prev parent reply other threads:[~2024-01-25 16:50 UTC|newest]
Thread overview: 72+ messages / expand[flat|nested] mbox.gz Atom feed top
2024-01-23 15:26 [ruby-core:116382] " byroot (Jean Boussier) via ruby-core
2024-01-23 18:32 ` [ruby-core:116386] " matheusrich (Matheus Richard) via ruby-core
2024-01-24 4:38 ` [ruby-core:116390] " mame (Yusuke Endoh) via ruby-core
2024-01-24 9:34 ` [ruby-core:116396] " zverok (Victor Shepelev) via ruby-core
2024-01-24 9:44 ` [ruby-core:116397] " byroot (Jean Boussier) via ruby-core
2024-01-24 9:46 ` [ruby-core:116398] " byroot (Jean Boussier) via ruby-core
2024-01-24 11:11 ` [ruby-core:116404] " duerst via ruby-core
2024-01-24 11:12 ` [ruby-core:116405] " Eregon (Benoit Daloze) via ruby-core
2024-01-24 11:14 ` [ruby-core:116406] " byroot (Jean Boussier) via ruby-core
2024-01-24 14:22 ` [ruby-core:116410] " mame (Yusuke Endoh) via ruby-core
2024-01-24 15:34 ` [ruby-core:116411] " byroot (Jean Boussier) via ruby-core
2024-01-24 16:28 ` [ruby-core:116412] " jeremyevans0 (Jeremy Evans) via ruby-core
2024-01-24 17:14 ` [ruby-core:116414] " Dan0042 (Daniel DeLorme) via ruby-core
2024-01-24 17:29 ` [ruby-core:116416] " rubyFeedback (robert heiler) via ruby-core
2024-01-24 17:46 ` [ruby-core:116417] " byroot (Jean Boussier) via ruby-core
2024-01-24 17:46 ` [ruby-core:116418] " palkan (Vladimir Dementyev) via ruby-core
2024-01-24 18:13 ` [ruby-core:116419] " byroot (Jean Boussier) via ruby-core
2024-01-24 18:53 ` [ruby-core:116420] " tenderlovemaking (Aaron Patterson) via ruby-core
2024-01-24 18:56 ` [ruby-core:116421] " tenderlovemaking (Aaron Patterson) via ruby-core
2024-01-24 19:42 ` [ruby-core:116422] " palkan (Vladimir Dementyev) via ruby-core
2024-01-24 19:45 ` [ruby-core:116423] " palkan (Vladimir Dementyev) via ruby-core
2024-01-24 19:48 ` [ruby-core:116424] " kddnewton (Kevin Newton) via ruby-core
2024-01-24 19:49 ` [ruby-core:116425] " byroot (Jean Boussier) via ruby-core
2024-01-24 19:57 ` [ruby-core:116426] " palkan (Vladimir Dementyev) via ruby-core
2024-01-24 21:36 ` [ruby-core:116427] " Dan0042 (Daniel DeLorme) via ruby-core
2024-01-24 21:58 ` [ruby-core:116428] " byroot (Jean Boussier) via ruby-core
2024-01-24 22:25 ` [ruby-core:116429] " jeremyevans0 (Jeremy Evans) via ruby-core
2024-01-24 22:32 ` [ruby-core:116430] " byroot (Jean Boussier) via ruby-core
2024-01-25 11:30 ` [ruby-core:116442] " Eregon (Benoit Daloze) via ruby-core
2024-01-25 12:18 ` [ruby-core:116443] " byroot (Jean Boussier) via ruby-core
2024-01-25 13:53 ` [ruby-core:116444] " Dan0042 (Daniel DeLorme) via ruby-core
2024-01-25 14:33 ` [ruby-core:116445] " byroot (Jean Boussier) via ruby-core
2024-01-25 15:32 ` [ruby-core:116446] " Dan0042 (Daniel DeLorme) via ruby-core
2024-01-25 15:43 ` [ruby-core:116447] " byroot (Jean Boussier) via ruby-core
2024-01-25 16:49 ` byroot (Jean Boussier) via ruby-core [this message]
2024-02-14 7:30 ` [ruby-core:116733] " matz (Yukihiro Matsumoto) via ruby-core
2024-02-14 9:08 ` [ruby-core:116735] " byroot (Jean Boussier) via ruby-core
2024-02-14 14:48 ` [ruby-core:116753] " Dan0042 (Daniel DeLorme) via ruby-core
2024-02-14 16:28 ` [ruby-core:116759] " byroot (Jean Boussier) via ruby-core
2024-03-19 17:17 ` [ruby-core:117232] " Dan0042 (Daniel DeLorme) via ruby-core
2024-03-19 19:00 ` [ruby-core:117234] " byroot (Jean Boussier) via ruby-core
2024-03-19 20:37 ` [ruby-core:117236] " Dan0042 (Daniel DeLorme) via ruby-core
2024-03-23 19:32 ` [ruby-core:117299] " Eric Wong via ruby-core
2024-05-06 18:00 ` [ruby-core:117782] " headius (Charles Nutter) via ruby-core
2024-05-06 18:58 ` [ruby-core:117784] " byroot (Jean Boussier) via ruby-core
2024-05-20 1:37 ` [ruby-core:117930] " byroot (Jean Boussier) via ruby-core
2024-05-23 17:53 ` [ruby-core:117988] " Dan0042 (Daniel DeLorme) via ruby-core
2024-05-23 18:00 ` [ruby-core:117989] " byroot (Jean Boussier) via ruby-core
2024-05-24 12:42 ` [ruby-core:117998] " Eregon (Benoit Daloze) via ruby-core
2024-05-24 12:46 ` [ruby-core:117999] " Eregon (Benoit Daloze) via ruby-core
2024-05-24 13:19 ` [ruby-core:118000] " Eregon (Benoit Daloze) via ruby-core
2024-05-24 21:04 ` [ruby-core:118009] " Dan0042 (Daniel DeLorme) via ruby-core
2024-06-03 22:30 ` [ruby-core:118162] " hartator (Julien Khaleghy) via ruby-core
2024-10-09 3:18 ` [ruby-core:119486] " hsbt (Hiroshi SHIBATA) via ruby-core
2024-10-09 4:12 ` [ruby-core:119489] " mame (Yusuke Endoh) via ruby-core
2024-10-09 5:37 ` [ruby-core:119490] " mame (Yusuke Endoh) via ruby-core
2024-10-09 12:54 ` [ruby-core:119493] " byroot (Jean Boussier) via ruby-core
2024-10-11 5:10 ` [ruby-core:119508] " mame (Yusuke Endoh) via ruby-core
2024-10-11 10:12 ` [ruby-core:119510] " byroot (Jean Boussier) via ruby-core
2024-10-14 9:54 ` [ruby-core:119521] " byroot (Jean Boussier) via ruby-core
2024-10-21 10:05 ` [ruby-core:119567] " pdfrod (Pedro Rodrigues) via ruby-core
2024-10-21 10:34 ` [ruby-core:119570] " byroot (Jean Boussier) via ruby-core
2024-12-17 4:14 ` [ruby-core:120274] " mame (Yusuke Endoh) via ruby-core
2024-12-17 8:45 ` [ruby-core:120275] " byroot (Jean Boussier) via ruby-core
2024-12-17 13:17 ` [ruby-core:120278] " kddnewton (Kevin Newton) via ruby-core
2024-12-19 1:53 ` [ruby-core:120310] " ko1 (Koichi Sasada) via ruby-core
2024-12-19 2:39 ` [ruby-core:120311] " hsbt (Hiroshi SHIBATA) via ruby-core
2024-12-19 4:48 ` [ruby-core:120312] " austin (Austin Ziegler) via ruby-core
2024-12-19 7:50 ` [ruby-core:120319] " byroot (Jean Boussier) via ruby-core
2024-12-19 8:09 ` [ruby-core:120321] " byroot (Jean Boussier) via ruby-core
2024-12-19 8:25 ` [ruby-core:120323] " byroot (Jean Boussier) via ruby-core
2024-12-19 15:22 ` [ruby-core:120329] " Dan0042 (Daniel DeLorme) via ruby-core
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=redmine.journal-106473.20240125164953.7941@ruby-lang.org \
--to=ruby-core@ml.ruby-lang.org \
--cc=noreply@ruby-lang.org \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for NNTP newsgroup(s).