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) about 6 years ago
          Updated by Eregon (Benoit Daloze) about 6 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 Dan0042 (Daniel DeLorme) about 6 years ago
          Updated by Dan0042 (Daniel DeLorme) about 6 years ago
          
          
        
        
      
      - Description updated (diff)
        
           Updated by jeremyevans0 (Jeremy Evans) about 6 years ago
          Updated by jeremyevans0 (Jeremy Evans) about 6 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) almost 2 years ago
          Updated by Dan0042 (Daniel DeLorme) almost 2 years 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) almost 2 years ago
          Updated by jeremyevans0 (Jeremy Evans) almost 2 years ago
          
          
        
        
      
      - Status changed from Open to Closed