
Issue #19470 has been updated by headius (Charles Nutter). This looks like a pretty typical effect of slicing, so I'm not sure there's something to "fix" here. JRuby has similar behavior. Improving the sharing heuristic might help, but it's hard to know where to draw the line; should a slice of 5 elements trigger copy? Ten elements? Perhaps only share when slice size is greater than some percentage of the total size? Perhaps we should make COW less hidden? Add something like `Array#fresh_slice` to opt into non-COW slicing when you know you're still mutating the original? ---------------------------------------- Bug #19470: Frequent small range-reads from and then writes to a large array are very slow https://bugs.ruby-lang.org/issues/19470#change-102119 * Author: giner (Stanislav German-Evtushenko) * Status: Open * Priority: Normal * ruby -v: ruby 3.2.1 (2023-02-08 revision 31819e82c8) [x86_64-linux] * Backport: 2.7: UNKNOWN, 3.0: UNKNOWN, 3.1: UNKNOWN, 3.2: UNKNOWN ---------------------------------------- Write to a large array gets very slow when done after range-reading more than 3 items. In such case the original array gets marked as shared which triggers CoW on a small change afterwards. This leads to a significant performance impact and high memory utilization in cases when we need to range-read/write from/to the same array many times. While this issue can be avoided by reading <= 3 elements at a time the main problem is that this behaviour is not obvious and hard to catch on on-trivial projects. ```ruby times = [] arr = [0] * 100000 times.push 0 100000.times do time_start = Time.now arr[5] = 100 # takes 0.01662315899999512 times[-1] += Time.now - time_start end times.push 0 100000.times do arr[0..2] time_start = Time.now arr[5] = 100 # takes 0.01826406799999659 times[-1] += Time.now - time_start end times.push 0 100000.times do arr[0..3] time_start = Time.now arr[5] = 100 # takes 7.757753919000069 times[-1] += Time.now - time_start end times.push 0 100000.times do arr.dup time_start = Time.now arr[5] = 100 # takes 7.626929300999957 times[-1] += Time.now - time_start end times.push 0 100000.times do arr.clone time_start = Time.now arr[5] = 100 # takes 8.216933763000046 times[-1] += Time.now - time_start end p times ``` -- https://bugs.ruby-lang.org/