Project

General

Profile

Actions

Feature #16049

closed

optimization for frozen dynamic string literals "#{exp}".dup and +"#{exp}"

Added by Dan0042 (Daniel DeLorme) over 4 years ago. Updated about 2 months ago.

Status:
Closed
Assignee:
-
Target version:
-
[ruby-core:94163]

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).

Actions #2

Updated by Dan0042 (Daniel DeLorme) over 4 years ago

  • Description updated (diff)
Actions #3

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) about 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?)

Actions #5

Updated by jeremyevans0 (Jeremy Evans) about 2 months ago

  • Status changed from Open to Closed
Actions

Also available in: Atom PDF

Like0
Like0Like0Like0Like0Like0