如何加速复杂图像处理?

12

每个用户可以上传100个黑白TIFF图像。

此过程需要:

  1. tif转换为jpg

  2. 将图像调整大小至xx。

  3. 将图像裁剪至200像素。

  4. 添加文本水印。

这是我的PHP代码:

move_uploaded_file($image_temp,$destination_folder.$image_name);
    
$image_name_only = strtolower($image_info["filename"]);

$name=$destination_folder.$image_name_only.".jpg";
$thumb=$destination_folder."thumb_".$image_name_only.".jpg";
$exec = '"C:\Program Files\ImageMagick-6.9.0-Q16\convert.exe" '.$destination_folder.$image_name. ' '.$name.' 2>&1';
exec($exec, $exec_output, $exec_retval);                

$exec = '"C:\Program Files\ImageMagick-6.9.0-Q16\convert.exe" '.$name. ' -resize 1024x  '.$name;
exec($exec, $exec_output, $exec_retval);

$exec = '"C:\Program Files\ImageMagick-6.9.0-Q16\convert.exe" '.$name. ' -thumbnail 200x200!  '.$thumb;
exec($exec, $exec_output, $exec_retval);

$exec = '"C:\Program Files\ImageMagick-6.9.0-Q16\convert.exe" '.$name. "  -background White  label:ش.پ12355  -append  ".$name;
exec($exec, $exec_output, $exec_retval);

这段代码有效,但每张图片的平均处理时间为1秒。因此对于100张图片,处理时间可能需要大约100秒。

如何加速整个过程(转换、调整大小、裁剪、水印)?

编辑

我有一台服务器 G8:内存32G,CPU:Intel Xeon E5-2650(4处理器)

版本:ImageMagick 6.9.0-3 Q16 x64

功能:OpenMP

convert logo: -resize 500% -bench 10 1.png

 Performance[1]: 10i 0.770ips 1.000e 28.735u 0:12.992
 Performance[2]: 10i 0.893ips 0.537e 26.848u 0:11.198
 Performance[3]: 10i 0.851ips 0.525e 27.285u 0:11.756
 Performance[4]: 10i 0.914ips 0.543e 26.489u 0:10.941
 Performance[5]: 10i 0.967ips 0.557e 25.803u 0:10.341
 Performance[6]: 10i 0.797ips 0.509e 27.737u 0:12.554
 Performance[7]: 10i 0.963ips 0.556e 25.912u 0:10.389
 Performance[8]: 10i 0.863ips 0.529e 26.707u 0:11.586

资源限制:

宽度:100MP;高度:100MP;面积:17.16GP;内存:7.9908GiB;映射:15.982GiB;磁盘:无限制;文件:1536;线程:8;限速:0;时间:无限制


2
更快的硬件...或分布式计算 - Honk der Hase
4
你是否总是在遇到一些只需要稍微动动脑筋或者简单阅读(并理解)文档就可以解决的问题后,仍然选择用更多的硬件和金钱去解决它们? - Kurt Pfeifle
1
虽然让用户“表现”更好通常是明智的事情,但对程序员来说,让自己的代码更有效地工作并避免浪费资源也是一件好事。尤其是如果他无法影响用户习惯的话... @PatrickBard - Kurt Pfeifle
@KurtPfeifle 当然,我完全同意。我只是觉得有时候我们会过度思考解决方案,而这些解决方案可能没有必要的要求,所以我想知道为什么它们是必要的。 - Patrick Bard
@ Lars Stegelit:我有一台功能强大的G8服务器,内存为32G,CPU为Intel Xeon E5-2650(4个处理器)。 - user4397664
4个回答

55

0. 两种方法

基本上,可以用两种不同的方式或二者结合来解决这个挑战:

  1. 尽可能巧妙地构建您的命令。
  2. 以速度为代价换取质量损失。

接下来的几个部分讨论这两种方法。

1. 检查你的ImageMagick版本:'Q8'、'Q16'、'Q32'或'Q64'?

首先,请检查您的确切ImageMagick版本并运行以下命令:

convert -version
如果您的 ImageMagick 版本中包含 Q16(或甚至 Q32 或 Q64,这是可能的,但过度的!),则这意味着所有 ImageMagick 的内部函数都将所有图像视为具有 16 位(或 32 位或 64 位)通道深度。 这会使图像处理质量更好,但需要双倍的内存与 Q8 相比,也会导致性能降低。 因此,建议测试一下切换到 Q8 构建所带来的性能提升。 无论如何,Q16 将使用两倍于 Q8 的 RAM 来处理每个图像,而 Q32 又将使用 Q16 的两倍。 这与输入文件中实际的每像素位数无关。请注意,保存为 16 位图像文件时,它们的磁盘空间也会比 8 位图像文件多。 如果使用 Q16 或 Q32,则始终要确保有足够的内存,否则性能将大幅降低。 对于一个1074 x 768像素的图像(宽 x 高),根据量子深度,将需要以下虚拟内存量:
Quantum                   Virtual Memory
  Depth    (consumed by 1 image 1024x768)
-------    ------------------------------  
      8         3.840 kiB  (=~  3,75 MiB)
     16         7.680 kiB  (=~  7,50 MiB)
     32        15.360 kiB  (=~ 14,00 MiB)
     

请注意,一些“优化”的处理管道(参见下文)需要在虚拟内存中保留图像的多个副本! 一旦虚拟内存无法满足可用RAM,系统将开始交换并从磁盘声明“内存”。 在这种情况下,所有聪明的命令管道优化都被抛弃了,并开始逆转。

ImageMagick的诞生是在CPU只能一次处理1位时代的。那是几十年前的事情了。 自那时以来,CPU架构发生了很大变化。 16位操作曾经需要比8位操作长两倍甚至更长时间。 然后出现了16位处理器。 16位操作成为标准。 CPU针对16位进行了优化: 突然间,有些8位操作甚至需要比16位等效操作更长时间。

如今,64位CPU已经很普遍。所以在实际情况下,Q8 vs. Q16 vs. Q32 的争论可能已经不存在了。 谁知道呢? 我不知道是否有关于这个问题的严肃基准测试报告。 如果有人(真正了解CPU和基准测试现实程序的深入了解)有一天运行这样的项目,这会很有趣。

是的,我看到你在Windows上使用Q16。 但是我仍然想提一下,为了完整起见... 将来会有其他用户阅读此问题和给出的答案。

非常可能,由于您的输入TIFF文件只有黑白色,因此Q8版本的图像质量输出对于您的工作流程来说已经足够好了。 (我只是不知道它是否也会明显更快: 这在很大程度上也取决于您运行此操作的硬件资源...)

此外,如果您的安装支持高动态分辨率图像(HDRI),这也可能导致一些速度惩罚。 谁知道呢? 因此,在使用配置选项--disable-hdri --with-quantum-depth=8构建IM可能会或可能不会导致速度提升。 没有人曾经以严肃的方式测试过这一点...... 我们所知道的唯一一件事是: 这些选项将降低图像质量。 然而,除非他们进行密切观察并进行直接逐个比较,否则大多数人甚至不会注意到这一点...

 

2. 检查ImageMagick的功能

接下来,检查您的ImageMagick安装是否带有OpenCL和/或OpenMP支持:

convert -list configure | grep FEATURES
如果有的话(像我的一样),你应该会看到类似这样的东西:
FEATURES      DPC HDRI OpenCL OpenMP Modules

OpenCL (for C omputing L anguage) 可以利用ImageMagick的并行计算功能(如果已编译),从而在图像处理操作中使用您计算机的GPU和CPU。

OpenMP(for M ulti-P rocessing)实现了类似的功能,它允许ImageMagick在系统的所有内核上并行执行。因此,如果您有一个四核系统并调整图像大小,则会在4个内核上(如果您有超线程,甚至是8个)进行调整大小。

The command

convert -version 

打印有关支持功能的基本信息。 如果OpenCL / OpenMP可用,则您将在输出中看到其中之一(或两者均存在)。

如果两者都没有显示出来: 请尝试获取已编译有OpenCL和/或OpenMP支持的最新版本的ImageMagick。

如果您从源代码自己构建软件包,请确保使用了OpenCL / OpenMP。 通过在'configure'步骤中包含适当的参数来实现这一点:

./configure  [...other options-]  --enable-openmp  --enable-opencl

ImageMagick关于OpenMP和OpenCL的文档在这里:

  • 使用OpenMP并行执行。 仔细阅读此文档,因为OpenMP不是万能的,在某些情况下可能无法发挥作用...
  • 使用OpenCL并行执行。 同上述内容适用于此处。此外,并非所有的ImageMagick操作都支持OpenCL。 此链接提供了支持OpenCL的操作列表,其中包括-resize

以下是从源代码构建和配置ImageMagick时的提示和说明,解释了各种选项:

此页面还简要讨论了--with-quantum-depth配置选项。

3. 对ImageMagick进行基准测试

现在,您还可以使用内置的-bench选项,使ImageMagick运行命令的基准测试。 例如:

convert logo: -resize 500% -bench 10 logo.png

  [....]
  Performance[4]: 10i 1.489ips 1.000e 6.420u 0:06.510
使用 -resize 500% 参数运行 ImageMagick 的 convert 命令,将内置的 IM logo: 图像在每个方向上缩放 500%。 -bench 10 部分告诉它在循环中运行相同的命令 10 次,然后打印性能结果:

  • 由于我启用了 OpenMP,因此有 4 个线程 (Performance[4]:)。
  • 报告它运行了 10 次迭代 (10i)。
  • 速度接近每秒 1.5 次迭代 (1.489ips)。
  • 总用户分配时间为 6.420 秒。

如果您的结果包括 Performance[1]: ,并且只有一行,则您的系统未启用 OpenMP。 (如果您的构建支持它,则可以通过运行 convert -limit thread 2 打开它。)

4. 调整 ImageMagick 的资源限制

查找有关您的系统的 ImageMagick 设置的 资源限制。 使用此命令:

identify -list resource
  File       Area     Memory     Map       Disk    Thread         Time
  --------------------------------------------------------------------
   384    8.590GB       4GiB    8GiB  unlimited         4    unlimited

上面显示了我的当前系统设置(不是默认设置--我曾经对它们进行了调整)。 这些数字是 ImageMagick 将使用的每个资源的最大量。您可以使用列标题中的每个关键字来优化您的系统。 为此,使用 convert -limit <resource> <number> 来将其设置为新限制。

也许的结果更像这样:

identify -list resource
  File       Area     Memory     Map       Disk    Thread         Time
  --------------------------------------------------------------------
   192    4.295GB       2GiB    4GiB  unlimited         1    unlimited
  • files 定义 ImageMagick 可使用的最大并发打开文件数。
  • memorymapareadisk 资源限制以字节为单位定义。 要将它们设置
    convert -limit thread 2
    

    首先启用2个并行线程作为第一步。 然后重新运行基准测试,并查看它是否真的会有所不同,如果有,那么差距有多大。 之后,您可以将限制设置为4甚至8,并重复这个过程...

    5. 使用Magick像素缓存(MPC)和/或Magick持久注册表(MPR)

    最后,您可以尝试使用ImageMagick像素缓存的特殊内部格式。 此格式称为MPC(Magick像素缓存)。 它仅存在于内存中。

    创建MPC时,处理的输入图像以未压缩的光栅格式保留在RAM中。 因此,MPC基本上是ImageMagick的本机内存中未压缩文件格式。 它只是直接内存转储到磁盘。 读取是从磁盘到内存的快速内存映射,根据需要进行(类似于内存页面交换)。 但不需要进行任何图像解码。

    (更多技术细节:MPC作为格式不可移植。 它也不适合作为长期归档格式。 它的唯一适用性是高性能图像处理的中间格式。 它需要两个文件来支持一个图像。)

    如果您仍然想将此格式保存到磁盘,请注意以下事项:

    • 图像属性会写入扩展名为 .mpc 的文件中。
    • 图像像素会写入带有扩展名 .cache 的文件中。

    它的主要优点是在以下情况下体现出来...

    1. ...处理非常大的图像,或者
    2. ...对同一图像应用多个操作在“操作管道”中。

    MPC专为与标准读取多次,写入一次相匹配的工作流模式而设计。

    一些人说,对于这样的操作,性能会提高,但我没有个人经验。

    首先将基础图片转换为MPC:

    convert input.jpeg input.mpc
    

    然后再运行:

    convert input.mpc [...your long-long-long list of crops and operations...]
    

    那么看看这是否显著节省您的时间。

    很可能您甚至可以使用此MPC格式“内联”(使用特殊的mpc:符号表示法,参见下文)。

    MPR格式(内存持久寄存器)执行类似操作。它将图像读入命名内存寄存器中。如果处理管道需要多次访问图像,则该管道还可以从该寄存器再次读取图像。图像在当前命令管道退出时保留在寄存器中。

    但我从未将此技术应用于实际问题,因此无法确定其在实际生活中的运作方式。

    6. 构建合适的IM处理管道以一步完成所有任务

    根据您描述的过程,它由4个不同的步骤组成:

    1. 将TIFF转换为JPEG。
    2. 将JPEG图像调整大小为xx (?? 值 ??)
    3. 将JPEG剪裁到200px。
    4. 添加文本水印。

    请告诉我是否正确理解您的意图:

    • 您有1个输入文件,即TIFF。
    • 您想要2个最终输出文件:
      1. 1个缩略图JPEG,大小为200x200像素;
      2. 1个带有1024像素宽度的标记JPEG(高度保持输入TIFF的纵横比);
      3. 1个(未标记的)JPEG仅是中间文件,您并不真正想要保留它。

    基本上,每个步骤都使用自己的命令--总共4个不同的命令。通过使用单个命令管道来执行所有步骤,这可以大大加快速度。

    此外,您似乎并不需要将未标记的JPEG作为最终结果保留--然而您用于生成它作为一个中间临时文件的唯一命令将其保存到了磁盘上。我们可以尝试跳过这个步骤,直接实现最终结果,而不需要这个额外的写入磁盘。

    有不同的方法可以实现此更改。我现在只会向您(和其他读者)显示一种--仅针对CLI,而不是PHP。我不是PHP专家——你需要把我的CLI方法转换成适当的PHP调用。

    (但请务必先确保真正理解更复杂的ImageMagick命令行的架构和结构!为此目标,请参考我其他答案中的内容:)

    您的4个步骤转化为以下单独的ImageMagick命令:

    convert image.tiff image.jpg
    
    convert image.jpg -resize 1024x image-1024.jpg
    
    convert image-1024.jpg -thumbnail 200x200 image-thumb.jpg
    
    convert -background white image-1024.jpg label:12345 -append image-labelled.jpg
    

    现在将这个工作流转换为一个单一的管道命令...以下命令可以实现此目的。它应该执行得更快(无论您在遵循上述步骤0.--4.时得到什么结果):

    现在要将这个工作流程转化为一个单一的管道命令,下面的命令可以做到这一点。它应该比你按照上述步骤0.--4.执行的结果更快:
    convert image.tiff                                                             \
     -respect-parentheses                                                          \
     +write mpr:XY                                                                 \
      \( mpr:XY                                       +write image-1024.jpg \)     \
      \( mpr:XY -thumbnail 200x200                    +write image-thumb.jpg \)    \
      \( mpr:XY -background white label:12345 -append +write image-labelled.jpg \) \
      null:
    

    说明:

    • -respect-parentheses: 需要使用此选项,使得 \( .... \) 括号内的子命令真正成为独立的命令。
    • +write mpr:XY: 用于将输入文件写入 MPR 存储器中。 XY 是一个标签(您可以使用任何名称),用于稍后重新调用相同的图像。
    • +write image-1024.jpg: 将在第一个括号对中执行的子命令的结果写入磁盘。
    • +write image-thumb.jpg: 将在第二个括号对中执行的子命令的结果写入磁盘。
    • +write image-labelled.jpg: 将在第三个括号对中执行的子命令的结果写入磁盘。
    • null:: 终止命令管道。如果没有这个选项,我们会以最后一个子命令的闭合括号结尾。

    7. 对比运行 4 个单独的命令和单一管道的性能差异

    为了对我的建议有一个粗略的感觉,我运行了下面的命令。

    第一个命令运行了 100 次 4 个独立命令的序列(并将所有生成的图像保存在不同的文件名下)。

    time for i in $(seq -w 1 100); do
       convert image.tiff                                                          \
                                                   image-indiv-run-${i}.jpg
       convert image-indiv-run-${i}.jpg -sample 1024x                              \
                                                   image-1024-indiv-run-${i}.jpg
       convert image-1024-indiv-run-${i}.jpg -thumbnail 200x200                    \
                                                   image-thumb-indiv-run-${i}.jpg
       convert -background white image-1024-indiv-run-${i}.jpg label:12345 -append \
                                                   image-labelled-indiv-run-${i}.jpg
       echo "DONE: run indiv $i ..."
    done
    

    我对4个单独的命令(重复100次!)的结果是这样的:

    real  0m49.165s
    user  0m39.004s
    sys   0m6.661s
    
    第二个命令对单个管道进行计时:
    time for i in $(seq -w 1 100); do
        convert image.tiff                                        \
         -respect-parentheses                                     \
         +write mpr:XY                                            \
          \( mpr:XY -resize 1024x                                 \
                    +write image-1024-pipel-run-${i}.jpg     \)   \
          \( mpr:XY -thumbnail 200x200                            \
                    +write image-thumb-pipel-run-${i}.jpg    \)   \
          \( mpr:XY -resize 1024x                                 \
                    -background white label:12345 -append         \
                    +write image-labelled-pipel-run-${i}.jpg \)   \
         null:
       echo "DONE: run pipeline $i ..."
    done
    

    单管道(重复100次!)的结果如下:

    real   0m29.128s
    user   0m28.450s
    sys    0m2.897s
    

    从结果可以看出,单个管道比4个单独的命令快约40%!

    现在你可以投资于多CPU、更多内存和快速SSD硬件以进一步提高速度 :-)

    但首先将这个CLI方法转换成PHP代码...


    关于这个主题还有一些要说的话。 但是我的时间已经用完了。 我可能会在几天后回到这个答案,再更新一些内容...


    更新: 我不得不使用新的基准测试数字更新这个问题的答案: 最初我忘记了将-resize 1024x操作(我真笨!)包含在管道版本中。 虽然仍然存在性能提升,但不像之前那么大。


    8. 使用-clone 0在内存中复制图像

    这是另一个替代方案,可以尝试使用mpr:方法和上面建议的命名内存寄存器。

    它再次使用括号内的“旁路处理”-clone 0操作。 它的工作方式如下:

    1. convert从磁盘一次读取输入TIFF并加载到内存中。
    2. 每个-clone 0操作符都会复制第一个加载的图像(因为它在当前图像堆栈中具有索引0)。
    3. 总命令管道的每个“括号内”子管道对克隆执行某些操作。
    4. 每个+write操作将相应结果保存到磁盘。

    所以这里是基准测试的命令:

    time for i in $(seq -w 1 100); do
        convert image.tiff                                         \
         -respect-parentheses                                      \
          \( -clone 0 -thumbnail 200x200                           \
                      +write image-thumb-pipel-run-${i}.jpg    \)  \
          \( -clone 0 -resize 1024x                                \
                      -background white label:12345 -append        \
                      +write image-labelled-pipel-run-${i}.jpg \)  \
         null:
       echo "DONE: run pipeline $i ..."
    done
    

    我的结果:

    real   0m19.432s
    user   0m18.214s
    sys    0m1.897s
    

    令我惊讶的是,这比使用 mpr: 版本要快!

    9. 使用 -scale-sample 替代 -resize

    这个替代方案很可能会加速您的重新调整大小子操作。但它很可能会导致图像质量稍微变差(您需要验证是否可见差异)。

    关于 -resize-sample-scale 之间的区别的一些背景信息,请参见以下答案:

    我也试过了:

    time for i in $(seq -w 1 100); do
        convert image.tiff                                         \
         -respect-parentheses                                      \
          \( -clone 0 -thumbnail 200x200                           \
                      +write image-thumb-pipel-run-${i}.jpg    \)  \
          \( -clone 0 -scale 1024x                                 \
                      -background white label:12345 -append        \
                      +write image-labelled-pipel-run-${i}.jpg \)  \
         null:
       echo "DONE: run pipeline $i ..."
    done
    

    我的结果:

    real   0m16.551s
    user   0m16.124s
    sys    0m1.567s
    

    这是目前为止最快的结果(我将其与+clone变体结合使用)。

    当然,这种修改也可以应用于您最初运行4个不同命令的方法。

    10. 通过在命令中添加-depth 8来模拟Q8构建。

    我实际上没有运行和测试过这个命令,但完整的命令应该是:

    time for i in $(seq -w 1 100); do
        convert image.tiff                                            \
         -respect-parentheses                                         \
          \( -clone 0 -thumbnail 200x200 -depth 8                     \
                      +write d08-image-thumb-pipel-run-${i}.jpg    \) \
          \( -clone 0 -scale 1024x       -depth 8                     \
                      -background white label:12345 -append           \
                      +write d08-image-labelled-pipel-run-${i}.jpg \) \
         null:
       echo "DONE: run pipeline $i ..."
    done
    

    这种修改方法也适用于你最初的“我运行4个不同命令”的方式。

    11. 使用GNU parallel结合Mark Setchell的建议进行组合

    当然,只有在您的整体工作流程允许这种并行处理时,才适合您使用此方法。

    对于我的小型基准测试来说,它是适用的。对于您的 Web 服务,可能一次只能处理一个任务...

    time for i in $(seq -w 1 100); do                                 \
        cat <<EOF
        convert image.tiff                                            \
          \( -clone 0 -scale  1024x         -depth 8                  \
                      -background white label:12345 -append           \
                      +write d08-image-labelled-pipel-run-${i}.jpg \) \
          \( -clone 0 -thumbnail 200x200  -depth 8                    \
                      +write d08-image-thumb-pipel-run-${i}.jpg   \)  \
           null:
        echo "DONE: run pipeline $i ..."
    EOF
    done | parallel --will-cite
    

    结果:

    real  0m6.806s
    user  0m37.582s
    sys   0m6.642s
    

    用户时间user和实际时间real的表面矛盾可以解释为: user时间表示在8个不同的CPU核心上计时的所有时间间隔之和。

    从用户看到的手表上的角度来看,时间过得更快:不到10秒钟。

    12. 总结

    选择自己喜欢的方法--结合不同的方法:

    1. 通过构建更聪明的命令管道可以获得一些加速(与当前图像质量相同), 避免运行各种命令(其中每个convert导致一个新的进程,并且必须从磁盘读取其输入)。 将所有图像处理打包成一个单一的进程。 利用“带括号的侧向处理”。 利用-clonembr:mbc:甚至组合每一个。

    2. 可以通过在性能和图像质量之间做出权衡来进一步提高速度: 您的一些选择是:

      1. -depth 8(必须在OP的系统上声明)vs。-depth 16(OP的系统上的默认值)
      2. -resize 1024 vs. -sample 1024x vs. -scale 1024x
    3. 如果您的工作流允许,则可以使用GNUparallel


我有一台服务器G8:内存:32G,CPU:Intel Xeon E5-2650(4个处理器),镜像位置:外置硬盘2T版本:ImageMagick 6.9.0-3 Q16 x64 - user4397664
当运行类似上述的长命令时,使用GNU Parallel的Bash函数可能会提高可读性:http://www.gnu.org/software/parallel/man.html#EXAMPLE:-Calling-Bash-functions - Ole Tange
有没有一种文件类型比另一种更快生成?我注意到JPG要快得多,但实际上比PNG大... - John Pollard
如果有人盲目地复制了上面的任何建议:一定要先测试!我只是想尝试在我的脚本中使用“5.使用Magick像素缓存(MPC)”,其中我制作了一个背景图像(PNG或动画GIF)和上面的文本层(使用自定义字体)。首先将我的背景转换为MPC,然后使用这些文件而不是原始的PNG或GIF文件,我的脚本甚至需要比使用非MPC文件更长约10%的时间来进行转换。因此,对我来说最好使用PNG和GIF。尽管如此,感谢您详细的答案,@Kurt Pfeifle!非常有趣的阅读。 - Arvid
我在Digital Ocean服务器上:当我运行 identify -list resource 时,结果是 ->宽度:16KP 高度:16KP 列表长度:18.446744EP 区域:128MP 内存:256MiB 映射:512MiB 磁盘:1GiB 文件:768 线程:2 节流阀:0 时间:无限制这很低吗?如何增加它? - dharmx
@dharmx:如何设置限制是答案的一部分。请参见其中的第4节。 - Kurt Pfeifle

5
一如既往,@KurtPfeifle提供了一个优秀的、理性而且解释得非常清楚的答案,他所说的所有建议都是可靠的,你应该认真听从并仔细遵循。
不过还有更多可以做的,但我不能在评论中添加太多,所以我把它作为另一个答案,尽管这只是对Kurt的增强...
我不知道Kurt使用了什么尺寸的输入图像,所以我制作了一个3000x2000的图像,并将我的运行时间与他的运行时间进行了比较,以查看它们是否可比,因为我们有不同的硬件。在我的机器上,单个命令运行了42秒,而流水线命令运行了36秒,所以我想我的图像大小和硬件大致相似。
然后我使用GNU Parallel在并行模式下运行了这些作业——我认为在Xeon上你会从中获得很多好处。以下是我的操作...
time for i in $(seq -w 1 100); do
    cat <<EOF
    convert image.tiff                                        \
     -respect-parentheses                                     \
     +write mpr:XY                                            \
      \( mpr:XY -resize 1024x                                 \
                +write image-1024-pipel-run-${i}.jpg     \)   \
      \( mpr:XY -thumbnail 200x200                            \
                +write image-thumb-pipel-run-${i}.jpg    \)   \
      \( mpr:XY -background white label:12345 -append         \
                +write image-labelled-pipel-run-${i}.jpg \)   \
     null:
   echo "DONE: run pipeline $i ..."
EOF
done | parallel

如您所见,我所做的就是将需要执行的命令打印到标准输出并将它们管道传递给GNU Parallel。以这种方式运行,在我的机器上只需要10秒钟。
我还尝试使用ffmpeg模拟该功能,并针对我的测试图像得出了以下结果-您的效果可能会有所不同。
#!/bin/bash
for i in $(seq -w 1 100); do
    echo ffmpeg -y -loglevel panic -i image.tif ff-$i.jpg 
    echo ffmpeg -y -loglevel panic -i image.tif -vf scale=1024:682 ff-$i-1024.jpg
    echo ffmpeg -y -loglevel panic -i image.tif -vf scale=200:200 ff-$i-200.jpg
done | parallel

在我的iMac上,使用一个3000x2000像素的image.tif输入文件,它可以在7秒内运行。

我试图在homebrew环境下安装libturbo-jpeg和ImageMagick却失败了。


1
太棒了!马克,你的回答非常出色!实际上,当我暗示其他可探索的选项时,GNU parallel 就是我心中想到的其中之一。然而,我目前对 parallel 的经验不多,在这台当前的机器上它还没有安装(但很快会安装)。我还有几个其他选项要进行基准测试。今晚或明晚会提供更多详细信息。 - Kurt Pfeifle

4

我禁不住要试试使用libvips进行基准测试。我使用了以下两个脚本:

#!/bin/bash

convert image.tiff                                                \
      \( -clone 0 -scale 1024x -depth 8 label:12345 -append       \
                  +write d08-image-labelled-pipel-run-$1.jpg \)   \
      \( -clone 0 -thumbnail 200x200 -depth 8                     \
                  +write d08-image-thumb-pipel-run-$1.jpg   \)    \
       null:

以及使用Python接口的libvips

#!/usr/bin/python3

import sys
import pyvips

image = pyvips.Image.thumbnail("image.tiff", 1024)
txt = pyvips.Image.text("12345")
image = image.join(txt, "vertical")
image.write_to_file(f"{sys.argv[1]}-labelled.jpg")

image = pyvips.Image.thumbnail("image.tiff", 200)
image.write_to_file(f"{sys.argv[1]}-thumb.jpg")

在使用带有libjpeg-turbo的IM 6.9.10-86和vips 8.11处理3000 x 2000 RGB tiff图像时,我看到:

$ /usr/bin/time -f %M:%e parallel ../im-bench.sh ::: {1..100}
78288:1.83
$ VIPS_CONCURRENCY=1 /usr/bin/time -f %M:%e parallel ../vips-bench.py ::: {1..100}
59512:0.84

因此,libvips的速度大约是原来的两倍,并且使用的内存更少。

(我最初在2015年发布了这篇文章。我已经使用当前版本的libvips更新了它,并在现代PC上重新运行,因此与上面的原始数据相比,处理速度有了巨大的提升)


3

我听到有些人说,GraphicsMagick(一个从ImageMagick分支出来的分支,已经有好几年了)比ImageMagick快得多。

因此,我抓住这个机会试用了一下。这就是我的第二个答案。

我运行了以下4个单独的gm命令的循环。这使结果可以与我其他答案中记录的4个单独的convert命令进行比较。它发生在同一台机器上:

time for i in $(seq -w 1 100); do 
 gm convert         image.tiff                         gm-${i}-image.jpg
 gm convert gm-${i}-image.jpg      -resize 1024x       gm-${i}-image-1024.jpg
 gm convert gm-${i}-image-1024.jpg -thumbnail 200x200  gm-${i}-image-thumb.jpg
 gm convert -background white    \
            gm-${i}-image-1024.jpg label:12345 -append gm-${i}-image-labelled.jpg
 echo "GraphicsMagick run no. $i ..."
done

结果时间:

real   1m4.225s
user   0m51.577s
sys    0m8.247s

这意味着,在这个特定的工作和这台机器上,我的Q8 GraphicsMagick(版本为1.3.20 2014-08-16 Q8)比我的Q16 ImageMagick(版本为6.9.0-0 Q16 x86_64 2014-12-06)慢,需要64秒,而后者只需要50秒,每个测试运行100次。

当然,这个简短的测试及其结果并不是绝对可靠的陈述。

你可能会问:在进行每个测试时,这台机器和它的操作系统还在做什么?同时加载了哪些其他应用程序?等等。你说得对。 - 但现在你可以自由地进行自己的测试。你可以做一件事来提供几乎相同的条件进行两个测试:在2个不同的终端窗口中同时运行它们!)


不错的尝试!我重新构建了我的ImageMagick,并使用Q8版本重新运行了测试,发现它比Q16版本稍微慢一些(约20%)。 - Mark Setchell
@MarkSetchell:现在再找一个优化,你就有40%了。再找五个,你就发明了时间旅行... :-) - Kurt Pfeifle

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接