Project

General

Profile

Actions

Feature #16291

open

Introduce support for resize in rb_ary_freeze and prefer internal use of rb_ary_freeze and rb_str_freeze for String and Array types

Added by methodmissing (Lourens Naudé) over 4 years ago. Updated 16 days ago.

Status:
Assigned
Target version:
-
[ruby-core:95665]

Description

References Github PR https://github.com/ruby/ruby/pull/2640

Why?

While working on https://github.com/ruby/ruby/pull/2037#issuecomment-548633141 I also looked at the rb_ary_freeze helper to determine if the same optimization of shrinking capacity can be applied there as well.

Further investigation revealed that rb_str_freeze attempts to resize / shrink strings on freeze, but we currently don't do the same for arrays despite API for it being exposed.

The gist of the change:

  • Let rb_ary_freeze also attempt to right size an array before freezing it
  • Replaced internal use of rb_obj_freeze and OBJ_FREEZE of callsites that pass a String or Array type in with rb_ary_freeze and rb_str_freeze specifically.

Results

Saves about 3MB of Array and String specific heap footprints on a redmine production env boot on Rails 6 (custom local upgrade - it does not officially support it yet):

Loading production environment (Rails 6.1.0.alpha)
irb(main):001:0> RUBY_DESCRIPTION
=> "ruby 2.7.0dev (2019-11-02T06:32:49Z master 772b0613c5) [x86_64-linux]"
irb(main):002:0> require 'objspace'
=> true
irb(main):003:0> GC.start
=> nil
irb(main):004:0> ObjectSpace.each_object(Array).sum {|o| ObjectSpace.memsize_of(o) }
=> 4451200
irb(main):005:0> ObjectSpace.each_object(String).sum {|o| ObjectSpace.memsize_of(o) }
=> 8608472
irb(main):006:0> exit
Loading production environment (Rails 6.1.0.alpha)
irb(main):001:0> RUBY_DESCRIPTION
=> "ruby 2.7.0dev (2019-11-02T16:52:34Z obj-freeze-specifi.. 3bc4048899) [x86_64-linux]"
irb(main):002:0> require 'objspace'
=> true
irb(main):003:0> GC.start
=> nil
irb(main):004:0> ObjectSpace.each_object(Array).sum {|o| ObjectSpace.memsize_of(o) }
=> 3233432
irb(main):005:0> ObjectSpace.each_object(String).sum {|o| ObjectSpace.memsize_of(o) }
=> 6748422

irb - the best calculator ...

irb(main):002:0> (4451200 - 3233432) / 1024
=> 1189
irb(main):003:0> (8608472 - 6748422) / 1024
=> 1816

Open questions

  • Would it make sense to let rb_obj_freeze switch on object type instead and apply this optimization without "polluting" internals with custom callsite changes? Native extensions can then benefit from the resize feature too.
  • HOWEVER: Discovered in testing that rb_str_freeze can fail asserts in rb_str_tmp_frozen_release when sprinkled too generously especially in io.c. And other issues may lurk too, thus incorporating the resizing in rb_obj_freeze won't work. See https://github.com/ruby/ruby/pull/2640#issuecomment-549119359

I think it could also make sense to split the PR in 2:

  • One changeset for rb_ary_freeze attempting to resize Arrays on freeze, special case internal rb_obj_freeze of Arrays to prefer the new API
  • Apply rb_str_resize at internal call sites where rb_obj_freeze is used with String types

Thoughts?

Actions

Also available in: Atom PDF

Like0
Like0Like0Like0Like0Like0Like0Like0