Как бороться с утечками памяти в RMagick в Ruby?

Im разрабатывает веб-приложение с Merb и im ищет некоторую безопасную и стабильную библиотеку обработки изображений. Я работал с Imagick в php, затем перешел на рубин и начал использовать RMagick. Но есть проблема. Длинные сценарии, вызывающие утечки памяти. Существует пару решений, но я не знаю, какой из них наиболее стабилен. Итак, что вы думаете?

В настоящее время мое приложение использует внутренний API, который я написал для обработки образов, в PHP. Он работает на отдельном сервере вместе с другими приложениями, поэтому это не большая проблема. Но я думаю, что это не хорошая архитектура.

В любом случае, я буду рассматривать любые практические советы.

Ответы

Ответ 1

Я тоже столкнулся с этой проблемой - решение заключается в принудительной сборке мусора.

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

В более поздних версиях RMagick я также считаю, что вы также можете назвать destroy! на изображении, когда вы закончили его обработку.

Комбинация этих двух будет, вероятно, гарантировать, что вы охвачены, но я не уверен в реальном влиянии на производительность (я бы предположил, что в большинстве случаев это незначительно).

В качестве альтернативы вы можете использовать mini-magick, который является оболочкой для клиента командной строки ImageMagick.

Ответ 2

При использовании RMagick важно помнить, как уничтожить изображение, как только вы закончите, иначе вы будете заполнять директорию /tmp при работе с большими наборами изображений. Например, вы должны вызвать destroy!

require 'RMagick'

Dir.foreach('/home/tiffs/') do |file|
    next if file == '.' or file == '..'
        image = Magick::Image.read(file).first
        image.format = "PNG"
        image.write("/home/png/#{File.basename(file, '.*')}.png")
        image.destroy!
end

Ответ 3

На самом деле, это не проблема Ruby, и другие интерпретаторы тоже это разделяют. Конкретная проблема заключается в том, что GC Ruby видит только память, которая была выделена самим Ruby, а не внешними библиотеками (за исключением библиотеки, использующей средства управления памятью Rubys). Таким образом, пространство ImageMagick-Object в Ruby действительно мало, но изображение в пространстве, управляемом ImageMagick, велико. Таким образом, это не утечка как таковая, но она ведет себя как одна. Rubys Garbage Collector никогда не срабатывает, если ваш Process остается на определенном пределе (стандартная 8MB). Поскольку ImageMagick никогда не создает большие объекты в пространстве Ruby, он, вероятно, никогда не срабатывает. Таким образом, вы используете предложенный метод создания нового процесса или использования exec. Другой довольно изящный - иметь службу обработки изображений в бэкэнд, которая вилки для каждой задачи. Другой из них - иметь какой-то мониторинг, который будет запускать GC каждый раз в то время.

Существует еще одна библиотека под названием MagickWand Тимоти Поля Хантера (автора RMagick), которая пытается решить эти проблемы и создать более приятную API. Это в альфа и требует довольно новой версии ImageMagick, однако.

Ответ 4

Это не из-за ImageMagick; это из-за самого Ruby, и это хорошо известная проблема. Мое предложение состоит в том, чтобы разделить вашу программу на две части: долговременную часть, которая выделяет небольшую память и просто имеет дело с управлением системой, и отдельную программу, которая фактически выполняет обработку. Длительный процесс управления должен сделать достаточно, чтобы найти какую-то работу для дочернего процесса, который он порождает, и ребенок должен выполнить всю обработку для этого конкретного рабочего элемента.

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

Это предполагает, что рабочие элементы довольно большие, что почти наверняка, если вы используете ImageMagick. Если это не так, вы обнаружите, что накладные расходы на создание нового процесса и повторный анализ интерпретатора Ruby всей вашей программы начинает становиться слишком большим. Вы можете справиться с этим, если ваша программа сделает больше рабочих единиц (скажем, десять или сто) до повторного выполнения.