Exploring Ruby's # frozen_string_literal
As of August 2024, Ruby 3.4 has not been released yet, although the Ruby Core team is working on it and continues to make progress on enhancements and refinements so our ruby apps can have the best performance. In this blog post we will talk about an interesting discussion happening about the # frozen_string_literal
comment at the top of Ruby files.
Magic performant comment
The magic comment # frozen_string_literal
pragma was introduced in Ruby 2.3. Mike Perham documented it and included benchmarks which showed performance improvements in Ruby code when using the comment to freeze strings.
frozen_string_literal reduces the generated garbage by ~100MB or ~20%! Free performance by adding a one line comment.
Once the comment # frozen_string_literal: true
has a true value set, it specifies that all string literals in the file will be frozen. Basically, when a string is frozen in Ruby, it means that its contents cannot be modified.
Testing it with Ruby 2.7.8:
In a micro scenario, we can benchmark and confirm that there is some performance improvement when we add this comment and set it to true
in a Ruby file.
require 'benchmark'
time_without_frozen = Benchmark.realtime do
1_000_000.times do
str = "Hello, World!"
str += " Adding some text."
end
end
The time to run this benchmark without frozen_string_literal
is 0.12221599998883903 seconds
# frozen_string_literal: true
require 'benchmark'
time_with_frozen = Benchmark.realtime do
1_000_000.times do
str = "Hello, World!"
str += " Adding some text."
end
end
The time to run this benchmark with frozen_string_literal
is 0.0953260000096634 seconds.
The magic comment had an impact of approximately 22% in run time in this case of a simple few characters string interpolation.
Performance in Ruby 3.4-dev:
Testing the following benchmark in the Ruby 3.4-dev
build on May 27th.
require 'benchmark'
n = 1_000_000
Benchmark.bmbm do |x|
x.report("with frozen_string_literal: true") { n.times do; str = "Hello, World!"; str += " Adding some text"; end }
end
We have the following results:
user system total real
without frozen_string_literal: true 0.103639 0.000471 0.104110 ( 0.106120)
user system total real
with frozen_string_literal: true 0.068708 0.000207 0.068915 ( 0.069655)
The benchmark still shows a better result using # frozen_string_literal: true
in Ruby 3.4, 0.036465s faster using the magic comment, approximately 34.37% of performance gain in this case.
Rubocop usage
Rubocop is a very popular library used in Ruby projects. It brings a cop, Style/FrozenStringLiteralComment
, that helps transitioning from mutable string literals to frozen string literals.
It will add the # frozen_string_literal: true magic comment to the top of files to enable frozen string literals. Frozen string literals may be default in future Ruby…The frozen string literal comment is only valid in Ruby 2.3+.
Note that the cop will accept files where the comment exists but is set to false instead of true.
StandardRB cop
There were already some discussions about having the Style/FrozenStringLiteralComment
cop always on, but the decision was never made.
In this Pull Request , there was a desire to bring this cop to life but it was never merged. StandardRB’s philosophy is that
“rules are not configurable and that every project styled by standard should be very nearly the same”
according to their contributor’s comment.
Behavior in Ruby 3.4?
There was a plan to make it the default behavior for Ruby 3.0. However, this plan was abandoned due to concerns about it being too disruptive as a breaking change without sufficient prior notice. Matz couldn’t agree to that due to HUGE compatibility issues this might cause.
At this time, Matz expressed a desire to enable # frozen_string_literal
by default in the future. However, any such change would require a careful migration plan due to backward compatibility concerns. Simply flipping the switch could break a significant amount of existing code or lead to significant performance loss.
The typical approach for such changes calls for a deprecation period, during which warnings are emitted in advance of the change.
What changes in Ruby 3.4:
- 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 theputchilledstring
opcode instead of the regularputstring
. - This opcode marks the string with a user flag and when these strings are mutated a warning is issued.
So you don’t need to run and delete the magic comment once you upgrade to Ruby 3.4, this Deprecation Warning will only be shown for files with no comment that are using putchilledstring
on compilation.
Releases are planned in 3 steps and final step could be Ruby 4.0.
- 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.
Conclusion
The # frozen_string_literal
magic comment is a very simple and good one-line of code that can help improve performance by making string literals immutable. Not every project needs it, and it can raise discussions about Ruby’s garbage collector or if the benchmark done to prove its concept is valid.
This is a simple change, but not strictly required for you to upgrade to Ruby 3.4 once it’s released. Making it default in Ruby 3.4 or newer versions, however, could bring interesting performance improvements and enhance the language.
Are you running EOL Ruby in production? We can help you get up to date! Contact us!