Bug #16351
closedWhy do Keyword arguments to initialize allocate a Hash, but not for other methods?
Description
Hello, while working on improving memory allocations in one of my apps, I stumbled upon the following behavior. I measured three different ways of passing variables to a new Object
, using plain params, using a hash and using keyword arguments.
class FooWithPlainParams
def initialize(a, b, c)
@a = a
@b = b
@c = c
end
end
class FooWithOptionsHash
def initialize(options = {})
@a = options[:a]
@b = options[:b]
@c = options[:c]
end
end
class FooWithKeyword
def initialize(a:, b:, c:)
@a = a
@b = b
@c = c
end
end
I used memory_profiler gem to measure the allocations with the attached script, calling new 100 times, using ruby 2.6.5p114 (2019-10-01 revision 67812) [x86_64-darwin19]
FooWithPlainParams
not surprisingly just reported "Total allocated: 4000 bytes (100 objects)".
FooWithOptionsHash
reported "Total allocated: 27200 bytes (200 objects)" with 100 Hash
allocations. This makes sense, since the params are passed on as a Hash
.
FooWithKeywordArguments
reported "Total allocated: 50400 bytes (300 objects)" with 200 Hash
allocations, which is a bit surprising.
After that I checked out ruby-head and there FooWithKeywordArguments.new
reports only 100 Hash
allocations as FooWithOptionsHash
. So that part seems to be fixed.
What surprised me so was, that using the same way of passing parameters in another method, resulted in no allocated Hash according to memory_profiler gem.
class FooWithKeyword
def foo(d:, e:, f:)
@d = d; @e = e; @f = f
end
end
Calling foo(d: 4, e: 5, f: 6)
on a FooWithKeyword
object, does not show any allocations.
What is the difference here between foo
and initialize
?
Files
Updated by brunoe (Bruno Escherl) about 5 years ago
- File profile_foo.rb profile_foo.rb added
Updated by brunoe (Bruno Escherl) about 5 years ago
- File deleted (
profile_initialize.rb)
Updated by brunoe (Bruno Escherl) about 5 years ago
- File profile_initialize.rb profile_initialize.rb added
Updated by nobu (Nobuyoshi Nakada) about 5 years ago
- Description updated (diff)
- Status changed from Open to Closed
FooWithKeyword#initialize
method is not called directly, but via Class#new
.
A Hash
is needed to delegate the keyword arguments.
By changing the last calling line as the following with making that initialize
method public, you could observe the same numbers as FooWithPlainParams
.
FooWithKeyword.allocate.initialize(a: 1, b: 2, c: 3)
Updated by mame (Yusuke Endoh) about 5 years ago
nobu (Nobuyoshi Nakada) wrote:
By changing the last calling line as the following with making that
initialize
method public, you could observe the same numbers asFooWithPlainParams
.FooWithKeyword.allocate.initialize(a: 1, b: 2, c: 3)
I'd like to add a note: allocate.initialize
just demonstrates the reason for the behavior. Never do write that in your code.
Currently, @ko1 (Koichi Sasada) is addressing this kind of issue fundamentally by a mechanism to write almost all builtin methods in Ruby. If Class#new is written in Ruby, and if a lazy allocation of keyword rest hash is introduced, the extra allocation will be removed.