mirror of
https://github.com/ruby/ruby.git
synced 2025-08-15 13:39:04 +02:00
Fix crash when passing large keyword splat to method accepting keywords and keyword splat
The following code previously caused a crash: ```ruby h = {} 1000000.times{|i| h[i.to_s.to_sym] = i} def f(kw: 1, **kws) end f(**h) ``` Inside a thread or fiber, the size of the keyword splat could be much smaller and still cause a crash. I found this issue while optimizing method calling by reducing implicit allocations. Given the following code: ```ruby def f(kw: , **kws) end kw = {kw: 1} f(**kw) ``` The `f(**kw)` call previously allocated two hashes callee side instead of a single hash. This is because `setup_parameters_complex` would extract the keywords from the keyword splat hash to the C stack, to attempt to mirror the case when literal keywords are passed without a keyword splat. Then, `make_rest_kw_hash` would build a new hash based on the extracted keywords that weren't used for literal keywords. Switch the implementation so that if a keyword splat is passed, literal keywords are deleted from the keyword splat hash (or a copy of the hash if the hash is not mutable). In addition to avoiding the crash, this new approach is much more efficient in all cases. With the included benchmark: ``` 1 miniruby: 5247879.9 i/s miniruby-before: 2474050.2 i/s - 2.12x slower 1_mutable miniruby: 1797036.5 i/s miniruby-before: 1239543.3 i/s - 1.45x slower 10 miniruby: 1094750.1 i/s miniruby-before: 365529.6 i/s - 2.99x slower 10_mutable miniruby: 407781.7 i/s miniruby-before: 225364.0 i/s - 1.81x slower 100 miniruby: 100992.3 i/s miniruby-before: 32703.6 i/s - 3.09x slower 100_mutable miniruby: 40092.3 i/s miniruby-before: 21266.9 i/s - 1.89x slower 1000 miniruby: 21694.2 i/s miniruby-before: 4949.8 i/s - 4.38x slower 1000_mutable miniruby: 5819.5 i/s miniruby-before: 2995.0 i/s - 1.94x slower ```
This commit is contained in:
parent
93accfdf48
commit
c20e819e8b
3 changed files with 118 additions and 25 deletions
25
benchmark/vm_call_kw_and_kw_splat.yml
Normal file
25
benchmark/vm_call_kw_and_kw_splat.yml
Normal file
|
@ -0,0 +1,25 @@
|
|||
prelude: |
|
||||
h1, h10, h100, h1000 = [1, 10, 100, 1000].map do |n|
|
||||
h = {kw: 1}
|
||||
n.times{|i| h[i.to_s.to_sym] = i}
|
||||
h
|
||||
end
|
||||
eh = {}
|
||||
def kw(kw: nil, **kws) end
|
||||
benchmark:
|
||||
1: |
|
||||
kw(**h1)
|
||||
1_mutable: |
|
||||
kw(**eh, **h1)
|
||||
10: |
|
||||
kw(**h10)
|
||||
10_mutable: |
|
||||
kw(**eh, **h10)
|
||||
100: |
|
||||
kw(**h100)
|
||||
100_mutable: |
|
||||
kw(**eh, **h100)
|
||||
1000: |
|
||||
kw(**h1000)
|
||||
1000_mutable: |
|
||||
kw(**eh, **h1000)
|
Loading…
Add table
Add a link
Reference in a new issue