Feature #16049
closedoptimization for frozen dynamic string literals "#{exp}".dup and +"#{exp}"
Description
When the decision was made that frozen_string_literal: true
should also apply to dynamic string literals, it was mitigated with the following explanation:
"#{exp}".dup can be optimized so it won’t allocate extra objects like "...".freeze
https://docs.google.com/document/u/1/d/1D0Eo5N7NE_unIySOKG9lVj_eyXf66BQPM4PKp7NvMyQ/pub
However that does not appear to be the case currently.
Using this script that generates 100k String objects:
# frozen_string_literal: true
def allocated
GC.stat[:total_allocated_objects]
end
GC.disable
c = ARGV.shift.to_sym
x_eq_i = ARGV.shift=="i"
x = "x"
before = allocated
100_000.times do |i|
x = i.to_s if x_eq_i
case c
when :normal then v = "#{x}"
when :freeze then v = "#{x}".freeze
when :minus then v = -"#{x}"
when :dup then v = "#{x}".dup
when :plus then v = +"#{x}"
else raise
end
end
after = allocated
printf "%d\n", after-before
I get the following number of objects allocated
x= frozen_string_literal normal freeze minus dup plus
'x' false 100001 100001 100001 200001 100001
'x' true 100001 100001 100001 200001 200001
i false 200001 200001 299999 300001 200001
i true 200001 200001 200001 300001 300001
We can see that "#{x}".dup
and +"#{x}"
allocate an extra object per iteration
I also tested with x = i.to_s
to see if deduplication of 100k identical strings was different from 100k different strings. In addition to the expected extra strings created by i.to_s
, there's an additional 100k extra strings created for -"#{i}"
when frozen_string_literal is false??? There may also be a memory leak here because while the number of objects increases by x3, memory usage increases by x4.
Summary:
I expected "#{v}".dup
and +"#{v}"
to behave the same regardless of frozen_string_literal (and optimize down to just one allocation)
I expected "#{v}".freeze
and -"#{v}"
to behave the same regardless of frozen_string_literal (and optimize down to just one allocation)
but they do not. I think they should. It would be nice.
Updated by Eregon (Benoit Daloze) over 4 years ago
Part of the explanation:
String#freeze never allocates a new String, it just freezes the receiver in place.
String#-@ deduplicates/interns the String, to do so it needs to return a new String instance (unless it's already an interned String, or it cannot be interned because e.g., it has instance variables).
Updated by jeremyevans0 (Jeremy Evans) over 4 years ago
- Tracker changed from Bug to Feature
- ruby -v deleted (
ruby 2.7.0dev (2019-04-22 trunk 67701) [x86_64-linux]) - Backport deleted (
2.5: UNKNOWN, 2.6: UNKNOWN)
Updated by Dan0042 (Daniel DeLorme) 2 months ago
Dynamic string literals are not longer frozen, this can be closed.
(Would it be possible for the author to close their own ticket?)
Updated by jeremyevans0 (Jeremy Evans) 2 months ago
- Status changed from Open to Closed