我正在使用 Merb 开发 Web 应用程序,并且正在寻找一些安全稳定的图像处理库。我曾经在 php 中使用 Imagick,然后转向 ruby 并开始使用 RMagick。但有一个问题。长时间运行的脚本导致内存泄漏。有几个解决方案存在,但我不知道哪个是最稳定的。所以你怎么看?
现在,我的应用程序使用我用 PHP 编写的用于处理图像的内部 API。它与其他应用程序一起在单独的服务器上运行,所以它不是一个大问题。但我认为它不是一个好的架构。
无论如何,我会考虑任何实用的技巧。
我正在使用 Merb 开发 Web 应用程序,并且正在寻找一些安全稳定的图像处理库。我曾经在 php 中使用 Imagick,然后转向 ruby 并开始使用 RMagick。但有一个问题。长时间运行的脚本导致内存泄漏。有几个解决方案存在,但我不知道哪个是最稳定的。所以你怎么看?
现在,我的应用程序使用我用 PHP 编写的用于处理图像的内部 API。它与其他应用程序一起在单独的服务器上运行,所以它不是一个大问题。但我认为它不是一个好的架构。
无论如何,我会考虑任何实用的技巧。
我也遇到过这个问题 - 解决方案是强制垃圾收集。
当您将图像变量重新分配给新图像时,只需使用 GC.start 即可确保从内存中释放旧引用。
在以后的 RMagick 版本上,我也相信你也可以调用 destroy!完成处理后在图像上。
两者的结合可能会确保您被覆盖,但我不确定现实生活对性能的影响(我认为在大多数情况下可以忽略不计)。
或者,您可以使用mini-magick,它是 ImageMagick 命令行客户端的包装器。
使用 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
实际上,这并不是一个真正的 Ruby 特定问题,其他解释器也有同样的问题。具体问题是 Ruby 的 GC 只看到由 Ruby 本身分配的内存,而不是由外部库分配的内存(使用 Ruby 内存管理工具的库除外)。因此,Ruby 内存空间中的 ImageMagick-Object 确实很小,但 ImageMagick 管理的空间中的图像很大。所以,这本身不是泄漏,但它的行为就像一个泄漏。如果您的进程保持在某个限制以下(标准为 8MB),Rubys 垃圾收集器永远不会启动。由于 ImageMagick 从未在 Ruby 空间中创建大型对象,因此它可能永远不会启动。因此,您要么使用生成新进程的建议方法,要么使用 exec。另一个相当不错的方法是在后端有一个图像处理服务,它为每个任务分叉。
Timothy Paul Hunter(RMagick 的作者)还有另一个名为MagickWand的库,它试图解决这些问题并创建更好的 API。不过,它是 alpha 版本,需要相当新的 ImageMagick 版本。
现在您可以告诉 ImageMagick 应该使用哪个内存空间。我认为RMAGICK_ENABLE_MANAGED_MEMORY = true
并且GC.start
是您需要的。
MANAGED_MEMORY
If true, RMagick is using Ruby managed memory for all allocations. If false,
RMagick allocates memory for objects directly from the operating system. You can
enable RMagick to use Ruby managed memory (when built with ImageMagick 6.4.0-11
and later) by setting
RMAGICK_ENABLE_MANAGED_MEMORY = true
before requiring RMagick.
https://rmagick.github.io/constants.html
但是,image.destroy!
本身足以稳定内存消耗。
这不是因为 ImageMagick;这是由于 Ruby 本身造成的,这是一个众所周知的问题。我的建议是将您的程序分成两部分:一个分配少量内存并仅处理系统控制的长期运行部分,以及一个实际执行处理工作的单独程序。长时间运行的控制进程应该足以为它产生的子进程找到一些工作,并且子进程应该为该特定工作项完成所有处理。
另一种选择是将两者结合起来,但在工作单元完成后,使用exec
同一程序的新启动版本替换您的进程,该程序将搜索另一个工作项,处理它,然后再次执行自身。
这是假设工作项相当大,如果您使用 ImageMagick,它们几乎可以肯定。如果不是,您会发现生成一个新进程和让 Ruby 解释器重新解析您的整个程序的开销开始变得有点过大。您可以通过让您的程序在重新执行之前执行更多工作单元(例如,十个或一百个)来解决这个问题。