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

Управление памятью Ruby

Я использую Ruby некоторое время, и я нахожу, что для больших проектов он может занимать достаточное количество памяти. Каковы некоторые рекомендации по сокращению использования памяти в Ruby?

  • Пожалуйста, пусть каждый ответ имеет одну "лучшую практику" и позволяет сообществу голосовать за нее.
4b9b3361

Ответ 1

Не делайте этого:

def method(x)
  x.split( doesn't matter what the args are )
end

или это:

def method(x)
  x.gsub( doesn't matter what the args are )
end

Оба будут постоянно утечка памяти в рубине 1.8.5 и 1.8.6. (не уверен около 1.8.7, поскольку я не пробовал это, но я действительно надеюсь, что это исправлено.) Обходной путь глупый и предполагает создание локальной переменной. Вам не нужно использовать локальный, просто создайте его...

Такие вещи, почему я очень люблю рубиновый язык, но не уважают МРТ

Ответ 2

При работе с огромными массивами объектов ActiveRecord будьте очень осторожны... При обработке этих объектов в цикле, если на каждой итерации вы загружаете связанные объекты с использованием ActiveRecord has_many, принадлежит_to и т.д. - использование памяти сильно растет, потому что каждый объект, принадлежащий массиву, растет...

Следующий метод очень помог нам (упрощенный пример):

students.each do |student|
  cloned_student = student.clone
  ...
  cloned_student.books.detect {...}
  ca_teachers = cloned_student.teachers.detect {|teacher| teacher.address.state == 'CA'}
  ca_teachers.blah_blah
  ...
  # Not sure if the following is necessary, but we have it just in case...
  cloned_student = nil
end

В приведенном выше коде "cloned_student" - это объект, который растет, но поскольку он заканчивается в конце каждой итерации, это не проблема для огромного количества студентов. Если бы мы не делали "clone", переменная цикла "student" выросла бы, но поскольку она принадлежит к массиву - используемая им память никогда не будет выпущена до тех пор, пока объект массива существует.

Также работает другой подход:

students.each do |student|
  loop_student = Student.find(student.id) # just re-find the record into local variable.
  ...
  loop_student.books.detect {...}
  ca_teachers = loop_student.teachers.detect {|teacher| teacher.address.state == 'CA'}
  ca_teachers.blah_blah
  ...
end

В нашей производственной среде у нас был фоновый процесс, который не завершился один раз, потому что для него недостаточно 8 ГБ ОЗУ. После этого небольшого изменения он использует менее 1 ГБ для обработки того же объема данных...

Ответ 3

Не злоупотребляйте символами.

Каждый раз, когда вы создаете символ, ruby ​​помещает в него таблицу символов. Таблица символов - это глобальный хеш, который никогда не очищается.
Это не технически утечка памяти, но она ведет себя как одна. Символы не занимают много памяти, поэтому вам не нужно быть слишком параноидальным, но он платит за это.

Общая рекомендация: если вы действительно набрали символ в коде, это нормально (у вас есть только конечный код), но не вызывайте to_sym на динамически сгенерированные или введенные пользователем строки, так как это открывает дверь к потенциально постоянно растущему числу

Ответ 4

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

В качестве примера, когда вы загружаете изображение с помощью RMagick, весь битмап загружается в память внутри рубинового процесса. Это может быть 30 мега или около того в зависимости от размера изображения.
Однако, большая часть этой памяти была выделена самим RMagick. Все, что Ruby знает, это объект-оболочка, который является крошечным (1).
Ruby только думает, что он держится на крошечном объеме памяти, поэтому он не будет беспокоить запуск GC. На самом деле он держится на 30 мегабайтах.
Если вы зацикливаетесь на 10 изображений, вы можете быстро разрядить себя из памяти.

Предпочтительным решением является ручное сканирование библиотеки C для очистки самой памяти - у RMagick есть уничтожение! метод, который делает это. Однако, если ваша библиотека не работает, вам может потребоваться принудительно запустить GC самостоятельно, даже если это обычно не рекомендуется.

(1): расширения Ruby C имеют обратные вызовы, которые будут запускаться, когда рубиновая среда выполнения решит их освободить, поэтому память в конечном итоге будет успешно освобождена в какой-то момент, возможно, не скоро.

Ответ 5

Измерьте и определите, какие части вашего кода создают объекты, которые увеличивают использование памяти. Улучшите и измените свой код, а затем снова выполните измерение. Иногда вы используете драгоценные камни или библиотеки, которые используют много памяти и создают множество объектов.

Существует множество инструментов, таких как busy-administrator, которые позволяют проверять размер памяти объектов (в том числе внутри хэшей и массивов).

$ gem install busy-administrator

Пример # 1: MemorySize.of

require 'busy-administrator'

data = BusyAdministrator::ExampleGenerator.generate_string_with_specified_memory_size(10.mebibytes)

puts BusyAdministrator::MemorySize.of(data)
# => 10 MiB

Пример # 2: MemoryUtils.profile

Код

require 'busy-administrator'

results = BusyAdministrator::MemoryUtils.profile(gc_enabled: false) do |analyzer|
  BusyAdministrator::ExampleGenerator.generate_string_with_specified_memory_size(10.mebibytes)
end  

BusyAdministrator::Display.debug(results)

Вывод:

{
    memory_usage:
        {
            before: 12 MiB
            after: 22 MiB
            diff: 10 MiB
        }
    total_time: 0.406452
    gc:
        {
            count: 0
            enabled: false
        }
    specific:
        {
        }
    object_count: 151
    general:
        {
            String: 10 MiB
            Hash: 8 KiB
            BusyAdministrator::MemorySize: 0 Bytes
            Process::Status: 0 Bytes
            IO: 432 Bytes
            Array: 326 KiB
            Proc: 72 Bytes
            RubyVM::Env: 96 Bytes
            Time: 176 Bytes
            Enumerator: 80 Bytes
        }
}

Вы также можете попробовать ruby-prof и memory_profiler. Лучше тестировать и экспериментировать с разными версиями кода, чтобы вы могли измерять использование памяти и производительность каждой версии. Это позволит вам проверить, действительно ли ваша оптимизация работает или нет. Обычно вы используете эти инструменты в режиме разработки/тестирования и отключите их в процессе производства.