Подтвердить что ты не робот

Почему эта программа Ruby не возвращает кучную память в операционную систему?

Я пытаюсь понять, когда память, выделенная из кучи Ruby, возвращается в операционную систему. Я понимаю, что Ruby никогда не возвращает выделенную ему кучу памяти, но я все еще не уверен в поведении памяти кучи. то есть те объекты, которые не вписываются в 40-байтовый RVALUE.

Рассмотрим следующую программу, которая выделяет некоторые большие строки, а затем заставляет основной GC.

require 'objspace'

STRING_SIZE = 250

def print_stats(msg)
  puts '-------------------'
  puts msg
  puts '-------------------'
  puts "RSS: #{`ps -eo rss,pid | grep #{Process.pid} | grep -v grep | awk '{ print $1,"KB";}'`}"
  puts "HEAP SIZE: #{(GC.stat[:heap_sorted_length] * 408 * 40)/1024} KB"
  puts "SIZE OF ALL OBJECTS: #{ObjectSpace.memsize_of_all/1024} KB"
end

def run
  print_stats('START WORK')
  @data=[]
  600_000.times do
    @data <<  " "  * STRING_SIZE
  end
  print_stats('END WORK')
  @data=nil
end

run
GC.start
print_stats('AFTER FORCED MAJOR GC')

Запуск этой программы с помощью Ruby 2.2.3 на MRI дает следующий результат. После принудительного основного GC размер кучи будет таким, как ожидалось, но RSS не значительно снизился.

-------------------
START WORK
-------------------
RSS: 7036 KB
HEAP SIZE: 1195 KB
SIZE OF ALL OBJECTS: 3172 KB
-------------------
END WORK
-------------------
RSS: 205660 KB
HEAP SIZE: 35046 KB
SIZE OF ALL OBJECTS: 178423 KB
-------------------
AFTER FORCED MAJOR GC
-------------------
RSS: 164492 KB
HEAP SIZE: 35046 KB
SIZE OF ALL OBJECTS: 2484 KB

Сравните эти результаты со следующими результатами, когда мы выделяем один большой объект вместо многих меньших объектов.

def run
  print_stats('START WORK')
  @data = " " * STRING_SIZE * 600_000
  print_stats('END WORK')
  @data=nil
end

-------------------
START WORK
-------------------
RSS: 7072 KB
HEAP SIZE: 1195 KB
SIZE OF ALL OBJECTS: 3170 KB
-------------------
END WORK
-------------------
RSS: 153584 KB
HEAP SIZE: 1195 KB
SIZE OF ALL OBJECTS: 149064 KB
-------------------
AFTER FORCED MAJOR GC
-------------------
RSS: 7096 KB
HEAP SIZE: 1195 KB
SIZE OF ALL OBJECTS: 2483 KB

Обратите внимание на окончательное значение RSS. Кажется, мы освободили всю память, которую мы выделили для большой строки.

Я не уверен, почему второй пример освобождает память, но первый пример не работает, поскольку они оба выделяют память из кучи Ruby. Это одна ссылка, которая могла бы дать объяснение, но мне были бы интересны объяснения других.

Освобождение памяти обратно к ядру также имеет стоимость. Память пользовательского пространства распределители могут удерживать эту память (конфиденциально) в надежде, что она может быть повторно использовать в одном процессе и не возвращать его в ядро ​​для использовать в других процессах.

4b9b3361

Ответ 1

@joanbm имеет здесь очень хорошую точку. Его ссылка объясняет это довольно хорошо:

Ruby GC выпускает память постепенно, поэтому, когда вы делаете GC на 1 большой кусок памяти, на который ссылается 1 ссылка, он выпускает все, но когда есть много ссылок, GC будет выпускать память в маленьких чанках.

Несколько вызовов GC.start будут освобождать все больше и больше памяти в первом примере.


Вот две статьи, которые нужно копать глубже: