From: "byroot (Jean Boussier) via ruby-core" Date: 2026-11-03T19:19:33+00:00 Subject: [ruby-core:127317] [Ruby Feature#22501] Optimize `Hash.new` and `String.new` in `opt_new` Issue #22501 has been reported by byroot (Jean Boussier). ---------------------------------------- Feature #22501: Optimize `Hash.new` and `String.new` in `opt_new` https://bugs.ruby-lang.org/issues/22501 * Author: byroot (Jean Boussier) * Status: Open ---------------------------------------- ### Context Both `Hash` and `String` initializer have a `capacity:` argument, because in performance sensitive code, correctly pre-sizing containers can have a big performance impact when the final size is large, as it saves both a number of reallocations, but also a number of copying / rehashing. Unfortunately, until now, for smaller capacities, the cost of handling keyword arguments from a C function would tend to outweigh the gains from right-sizing, making it hard to use in cases where the capacity is entirely dynamic and potentially small. Another concern is from Variable Width Allocation. For performance it's preferable to allocate embedded objects when possible, which requires to know the size prior to call `#initialize`, as at that point the slot has already been allocated to the minimum size. Hence the `String.new(capacity:)` / `Hash.new` requires `new` to be a custom `new` method to extract the capacity argument and compute the ideal slot size, but doing so defeat the existing `opt_new` optimization, and cause `String#initialize` / `Hash#initialize` to not be called. ### Prior Art @tenderlovemaking opened [Feature #22130] with the idea of a dedicated instruction for `String.new` but it died off over various concerns, one of which was that adding too many dedicated instruction would risk producing worse code in the interpreter loop. ### Solution So instead with @jhawthorn we thought of a smaller change. - Define `Hash#initialize` in Ruby for more efficient keyword argument handling. - Add a path in the existing `opt_new` to peek the stack for the `capacity:` keyword argument and pass it to the allocator. - Let `opt_new` call `initialize` like it would for the default `new` method. ### Implementation The `Hash.new` optimization was merged as [7142a3debb](https://github.com/ruby/ruby/commit/7142a3debbda9c35bef9921fc59a27225969b979) with some decent gains, and `String.new` is being finalized in [GH-19674](https://github.com/ruby/ruby/pull/19674) ### Concerns However after the `Hash.new` optimization was merged a subtle behavioral difference was noticed. Until now, refining `Hash#initialize` would have no effect when calling `Hash.new`, but in 4.1 the refined method is invoked by the interpreter (but not by JITs): ```ruby module R refine Hash do def initialize(*) raise "Not called prior to 4.1 nor by JITs" end end end using R Hash.new ``` ### Question What is the correct behavior here? Should this refinement work? or not work? Or is it "implementation defined". -- https://bugs.ruby-lang.org/ ______________________________________________ ruby-core mailing list -- ruby-core@ml.ruby-lang.org To unsubscribe send an email to ruby-core-leave@ml.ruby-lang.org ruby-core info -- https://ml.ruby-lang.org/mailman3/lists/ruby-core.ml.ruby-lang.org/