Project

General

Profile

Bug #21214

Updated by mood_vuadensl (LOIC VUADENS) 2 days ago

Hello, 

 After updating Ruby from 3.3.6 to 3.4.2, our batch-style (not based on rails) application exceed its memory limit. 
 Below is an example script that runs on both versions and demonstrates that 'ObjectSpace.memsize_of_all' does not vary significantly, but the OS 'VmRSS' increases significantly. 

 Do you have any information on what might have caused this increase or any lead to reduce this peak? 

 Here is the result on a Linux 5.15.167.4-microsoft-standard-WSL2: 
 with Ruby 3.3.6: 
 ```text 
 ruby 3.3.6 (2024-11-05 revision 75015d4c1f) [x86_64-linux] 
 On start on 3.3.6 
 - OS VmRSS:          20616 20628 kB 
 - ObjectSpace.memsize_of_all: 1.7MB 1.73MB 

  On first full workload: 0 
 - OS VmRSS:         559212 521504 kB 
 - ObjectSpace.memsize_of_all: 327.86MB 289.74MB 

 .... 
 After workload 
 - OS VmRSS:         711776 573484 kB 
 - ObjectSpace.memsize_of_all: 327.86MB 251.59MB 

 After data released 
 - OS VmRSS:         616364 487404 kB 
 - ObjectSpace.memsize_of_all: 1.71MB 1.73MB 
 ``` 
 and 3.4.2: 
 ```text 
 ruby 3.4.2 (2025-02-15 revision d2930f8e7a) +PRISM [x86_64-linux] 
 On start on 3.4.2 
 - OS VmRSS:          13076 13132 kB 
 - ObjectSpace.memsize_of_all: 1.7MB 1.71MB 

  On first full workload: 0 
 - OS VmRSS:         674324 612508 kB 
 - ObjectSpace.memsize_of_all: 353.6MB 315.47MB 

 .... 
 After workload 
 - OS VmRSS:        1000628         725356 kB 
 - ObjectSpace.memsize_of_all: 327.85MB 

 251.57MB 


 After data released 
 - OS VmRSS:         843636 616364 kB 
 - ObjectSpace.memsize_of_all: 1.7MB 1.71MB 
 ``` 

 and the associated script: 
 ```ruby 
 require 'objspace' 

 BYTES_TO_MB = 1024 * 1024 

 $stdout.sync = true 
 srand(1) 

 # Declare supporting code 
 def print_info(context) 
   puts context 
   GC.start 

   os_mem_metric = File.readlines("/proc/#{Process.pid}/status").find { |line| line.start_with?('VmRSS:') } 
   puts "- OS #{os_mem_metric}" 
   puts "- ObjectSpace.memsize_of_all: #{(ObjectSpace.memsize_of_all.to_f/BYTES_TO_MB).round(2)}MB" 
   puts '' 
 end 

 def random_string = Array.new(10) { rand(99) }.join 

 class A 
   def initialize 
     @a = random_string 
     @b = rand(1000000000000) 
   end 
 end 


 # Main 
 print_info "On start on #{RUBY_VERSION}" 

 objects = Array.new(1_000_000) { A.new } 
 hashes =    Array.new(250_000) { { a: rand(100_000), b: rand(100_000), c: random_string } } 
 arrays =    Array.new(250_000) { [rand(100_000), rand(100_000), random_string] } 

 keep_if = ->(index) { index.even? } 
 0.upto(3) do |i_loop| 
   objects = objects.map.with_index { |obj, index| keep_if.call(index) ? obj : A.new } 
   hashes = hashes.map.with_index { |obj, index| keep_if.call(index) ? obj : { a: rand(10_000), b: rand(10_000), c: random_string rand(10_000) } } 
   arrays = arrays.map.with_index { |obj, index| keep_if.call(index) ? obj : [rand(10_000), rand(10_000), random_string] rand(10_000)] } 

   print_info " On first full workload: #{i_loop}" if i_loop.zero? 

   keep_if = ->(index) { index.odd? } if i_loop == 1 
   keep_if = ->(index) { index%5 == 0 } if i_loop == 2 
   keep_if = ->(index) { index.even? } if i_loop == 3 
   print '.' 
 end 
 puts '' 

 print_info 'After workload' 

 objects.clear 
 hashes.clear 
 arrays.clear 

 print_info 'After data released' 
 ``` 

 Regards 

Back