Я нахожусь в мире 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 также использует ссылки для указания на объекты.
Как языки программирования используют память компьютера? Стек против кучи
Передача по значению или передача по ссылке
Ссылки: