Я нахожусь в мире Ruby уже третью неделю, и я думаю, что пора немного изучить, как Ruby использует память, и реализовать несколько приемов, чтобы ускорить наши программы Ruby за счет использования меньшего объема памяти.
Среди популярных сегодня языков программирования выделение и освобождение памяти в C и C ++ выполняется вручную программистами. В других языках, таких как Java, Python и Ruby, память освобождается (когда объекты выходят за пределы области видимости) сборщиком мусора.
Знакомство со сборкой мусора Ruby - Ruby GC:
Что такое сборщик мусора? GC - это модуль Ruby, который предоставляет интерфейс к механизму сборки мусора Ruby (в частности: механизм маркировки и очистки начиная с Ruby 2.2). Два наиболее часто используемых и важных метода - это GC.stat
и GC.start
.
GC.start
по умолчанию инициирует сборку мусора, т. Е. Освобождает память.
GC.stat
показывает статистику сборщика мусора - если мы запустим GC.stat
в irb
, пример результата будет:
{ :count=>9, :heap_allocated_pages=>132, :heap_sorted_length=>133, :heap_allocatable_pages=>0, :heap_available_slots=>53798, :heap_live_slots=>53585, :heap_free_slots=>213, :heap_final_slots=>0, :heap_marked_slots=>23105, :heap_swept_slots=>13998, :heap_eden_pages=>132, :heap_tomb_pages=>0, :total_allocated_pages=>132, :total_freed_pages=>0, :total_allocated_objects=>164009, :total_freed_objects=>110424, :malloc_increase_bytes=>389720, :malloc_increase_bytes_limit=>16777216, :minor_gc_count=>6, :major_gc_count=>3, :remembered_wb_unprotected_objects=>210, :remembered_wb_unprotected_objects_limit=>394, :old_objects=>20937, :old_objects_limit=>37436, :oldmalloc_increase_bytes=>390184, :oldmalloc_increase_bytes_limit=>16777216 }
Ух! Информации много! По соображениям простоты, сейчас нам нужно обратить внимание только на два ключа: :total_allocated_objects
и :total_freed_objects
. Как следует из названий, первый показывает общее количество выделенных объектов, а второй - общее количество освобожденных объектов.
Кроме того, чтобы отслеживать использование памяти Ruby, нам нужен гем с именем get_process_mem
, чтобы получить информацию об использовании памяти процессом. Добавляем gem ‘get_process_mem’
в Gemfile и запускаем bundle instal
. Затем мы инициализируем новый объект GetProcessMem
и используем .mb
, чтобы вернуть использование памяти в МБ. Наша основная настройка среды тестирования:
require ‘get_process_mem’ GC.start allocated_before = GC.stat(:total_allocated_objects) freed_before = GC.stat(:total_freed_objects) mem = GetProcessMem.new puts “Memory usage before: #{mem.mb} MB.” # # some code to test # mem = GetProcessMem.new puts “Memory usage after: #{mem.mb} MB.” GC.start allocated_after = GC.stat(:total_allocated_objects) freed_after = GC.stat(:total_freed_objects) puts “Total objects allocated: #{allocated_after — allocated_before}” puts “Total objects freed: #{freed_after — freed_before}”
А теперь приступим к тестам!
Сначала давайте проверим массив, поскольку объекты сохраняются в массиве, давайте посмотрим, что произойдет, если мы перелопатим огромное количество объектов в массив:
array = [] 1_000_000.times do array << “a sample string” end
Вот результат:
Memory usage before: 11.70703125 MB. Memory usage after: 60.765625 MB. Total objects allocated: 1000119 Total objects freed: 140
Как мы видим, использование памяти значительно увеличивается, и освобождается лишь несколько объектов. Мы создали 1 000 000 копий "a sample string"
, но они не собираются сборщиком мусора, поскольку Ruby предполагает, что мы можем использовать их в будущем, потому что мы сохраняем объекты.
Но что произойдет, если мы все же сделаем 1 000 000 раз, но не сохраним их?
1_000_000.times do string = “a sample string” end
И вот результат:
Memory usage before: 11.5859375 MB. Memory usage after: 11.5859375 MB. Total objects allocated: 1000118 Total objects freed: 1000140
Теперь все объекты освобождены, а использование памяти до и после практически не меняется.
Хорошо, тогда что, если мы должны сделать огромную лопату в массив в качестве первого теста, и по-прежнему хотим использовать меньше памяти? Один из способов - вызвать freeze
в строке. freeze
- это встроенный метод, предотвращающий дальнейшие изменения объекта, поэтому его можно легко повторно использовать и ссылаться на него повторно:
array = [] 1_000_000.times do array << “a sample string”.freeze end
Результат:
Memory usage before: 11.63671875 MB. Memory usage after: 19.26953125 MB. Total objects allocated: 119 Total objects freed: 140
Как мы видим, хотя общее количество освобожденных объектов не сильно меняется, общее количество выделенных объектов и использование памяти намного меньше в нашем первом тесте. Итак, что происходит с Ruby, так это то, что вместо хранения 1 000 000 различных объектов Ruby сохраняет только один строковый объект с 1 000 000 ссылок на этот объект.
Кроме того, тот же результат может быть достигнут более распространенной практикой - просто присвоить строку переменной:
array = [] string = “a sample string” 1_000_000.times do array << string end
И результат:
Memory usage before: 11.57421875 MB. Memory usage after: 19.32421875 MB. Total objects allocated: 120 Total objects freed: 140
Работает как часы!
Замечательно. Теперь мы хотим изменить некоторые перечислимые объекты, как показано ниже:
array = [“cat”, “mice”, “dog”, “giraffe”,”elephant”] array *= 100_000 #simply copy the array 100000 times array.map do |element| element.gsub(/[aeiou]/, ‘*’).upcase end
Давайте еще раз проверим состояние памяти:
Memory usage before: 11.640625 MB. Memory usage after: 92.2578125 MB. Total objects allocated: 3500129 Total objects freed: 3500142
Итак, теперь есть способ уменьшить использование памяти и ускорить наш код? да. Начиная с Ruby 2.0, рубисты могут создавать счетчики _17 _ (помните, все программисты ленивы?). Эта замечательная функция может значительно улучшить использование памяти при связывании методов в перечислителе. Чтобы использовать эту функцию, мы просто добавляем lazy
в счетчик:
array = [“cat”, “mice”, “dog”, “giraffe”,”elephant”] array *= 100_000 #simply copy the array 100000 times array.lazy.map do |element| element.gsub(/[aeiou]/, ‘*’).upcase end
Давай посмотрим что происходит:
Memory usage before: 11.62109375 MB. Memory usage after: 15.28515625 MB. Total objects allocated: 136 Total objects freed: 149
Это так удобно и аккуратно! Добавляя такой небольшой фрагмент кода, мы можем сэкономить большое количество памяти!
Дополнительные чтения:
В Ruby все является объектом. Все объекты Ruby создаются в памяти Heap. Ruby также использует ссылки для указания на объекты.
Как языки программирования используют память компьютера? Стек против кучи
Передача по значению или передача по ссылке
Ссылки: