您是否曾觉得您的计算机运行速度不如预期?我过去常常有这种感觉,直到我发现了 GNU Parallel。
GNU Parallel 是一个用于并行执行任务的 shell 实用程序。它可以解析多个输入,从而同时针对多组数据运行您的脚本或命令。 您终于可以使用所有的 CPU 了!
如果您使用过 xargs
,那么您就已经知道如何使用 Parallel 了。 如果您没有使用过,那么本文将教您,以及许多其他的用例。
安装 GNU Parallel
您的 Linux 或 BSD 计算机可能没有预先安装 GNU Parallel。 从您的存储库或端口集合安装它。 例如,在 Fedora 上:
$ sudo dnf install parallel
或者在 NetBSD 上:
# pkg_add parallel
如果一切都失败了,请参考项目主页。
从串行到并行
顾名思义,Parallel 的优势在于它并行运行任务,而不是像我们许多人仍然做的那样,顺序运行。
当您针对多个对象运行一个命令时,您本质上是在创建一个队列。 一定数量的对象可以被命令处理,而所有其他的对象只是站在那里等待轮到它们。 这是低效的。 给定足够的数据,总会有一个队列,但是与其只有一个队列,为什么不拥有很多小队列呢?
假设您有一个文件夹,其中包含您想要从 JPEG 转换为 PNG 的图像。 有很多方法可以做到这一点。 有一种手动方法是在 GIMP 中打开每个图像并将其导出为新格式。 这通常是最糟糕的方法。 它不仅耗时,而且劳动密集。
这种主题的一个非常巧妙的变体是基于 shell 的解决方案:
$ convert 001.jpeg 001.png
$ convert 002.jpeg 002.png
$ convert 003.jpeg 003.png
... and so on ...
当您第一次学习它时,这是一个很棒的技巧,而且起初这是一个巨大的改进。 无需 GUI 和不断的点击。 但它仍然是劳动密集型的。
更好的是:
$ for i in *jpeg; do convert $i $i.png ; done
至少,这将使任务启动并让您腾出手来做更多有成效的事情。 问题是,它仍然是一个串行过程。 一个图像被转换,然后队列中的下一个图像进行转换,依此类推,直到队列被清空。
使用 Parallel:
$ find . -name "*jpeg" | parallel -I% --max-args 1 convert % %.png
这是两个命令的组合: find
命令,它收集您想要操作的对象;以及 parallel
命令,它对对象进行排序并确保所有内容都按要求进行处理。
find . -name "*jpeg"
查找当前目录中所有以jpeg
结尾的文件。parallel
调用 GNU Parallel。-I%
创建一个占位符,称为%
,用于代表find
传递给 Parallel 的任何内容。 您使用它是因为否则您必须手动为find
的每个结果编写一个新命令,而这正是您想要避免的。--max-args 1
限制 Parallel 从队列中请求新对象的速率。 由于 Parallel 正在运行的命令只需要一个文件,因此您将速率限制为 1。 如果您正在执行需要两个文件的更复杂的命令(例如cat 001.txt 002.txt > new.txt
),则您会将速率限制为 2。convert % %.png
是您想要在 Parallel 中运行的命令。
此命令的结果是 find
收集所有相关文件并将它们传递给 parallel
,后者启动一个任务并立即请求队列中的下一个文件。 Parallel 继续这样做,只要启动新任务而不会削弱您的计算机是安全的。 当旧任务完成时,它会用新任务替换它们,直到提供给它的所有数据都被处理完毕。 之前需要 10 分钟的时间可能只需要 5 分钟或 3 分钟使用 Parallel。
多个输入
只要您熟悉 find
和 xargs
(统称为 GNU Find Utilities 或 findutils
),find
命令就是 Parallel 的绝佳网关。 它提供了一个灵活的界面,许多 Linux 用户已经很熟悉,如果您是新手,也很容易学习。
find
命令非常简单:您向 find
提供要搜索的目录的路径以及您要搜索的文件名的一部分。 使用通配符来扩大您的搜索范围; 在此示例中,星号表示任何内容,因此 find
会找到所有以字符串 searchterm
结尾的文件。
$ find /path/to/directory -name "*searchterm"
默认情况下,find
一次返回其搜索结果的一个项目,每行一个项目:
$ find ~/graphics -name "*jpg"
/home/seth/graphics/001.jpg
/home/seth/graphics/cat.jpg
/home/seth/graphics/penguin.jpg
/home/seth/graphics/IMG_0135.jpg
当您将 find
的结果通过管道传递给 parallel
时,每行上的每个项目都被视为 parallel
仲裁的命令的一个参数。 另一方面,如果您需要在一条命令中处理多个参数,则可以拆分队列中数据传递给 parallel
的方式。
这是一个简单的、不切实际的例子,我稍后会将其变成更有用的东西。 只要您安装了 GNU Parallel,您就可以按照这个例子进行操作。
假设您有四个文件。 将它们逐行列出,以准确了解您拥有的内容:
$ echo ada > ada ; echo lovelace > lovelace
$ echo richard > richard ; echo stallman > stallman
$ ls -1
ada
lovelace
richard
stallman
您想要将两个文件合并为第三个文件,其中包含两个文件的内容。 这要求 Parallel 可以访问两个文件,因此在这种情况下 -I%
变量将不起作用。
Parallel 的默认行为基本上是不可见的:
$ ls -1 | parallel echo
ada
lovelace
richard
stallman
现在告诉 Parallel 您想要每个任务获取两个对象:
$ ls -1 | parallel --max-args=2 echo
ada lovelace
richard stallman
现在这些行已经合并了。 具体来说,来自 ls -1
的两个结果一次性传递给 Parallel。 这对于此任务来说是正确的参数数量,但它们现在实际上是一个参数:“ada lovelace” 和 “richard stallman”。 您实际想要的是每个任务有两个不同的参数。
幸运的是,这个技术问题由 Parallel 本身解析。 如果您将 --max-args
设置为 2
,您将获得两个变量 {1}
和 {2}
,它们表示参数的第一部分和第二部分:
$ ls -1 | parallel --max-args=2 cat {1} {2} ">" {1}_{2}.person
在此命令中,变量 {1}
是 ada 或 richard(取决于您查看哪个任务),而 {2}
是 lovelace
或 stallman
。 文件的内容使用带引号的重定向符号重定向(引号从 Bash 中获取重定向符号,以便 Parallel 可以使用它),并放入名为 ada_lovelace.person
和 richard_stallman.person
的新文件中。
$ ls -1
ada
ada_lovelace.person
lovelace
richard
richard_stallman.person
stallman
$ cat ada_*person
ada lovelace
$ cat ri*person
richard stallman
如果您整天都在解析数百兆字节大小的日志文件,您可能会看到并行文本解析对您有多么有用; 否则,这主要是一个演示练习。
但是,这种处理方式对于文本解析来说非常有价值。 这是一个来自电影界的真实例子。 考虑一个视频文件和音频文件的目录,这些文件需要连接在一起。
$ ls -1
12_LS_establishing-manor.avi
12_wildsound.flac
14_butler-dialogue-mixed.flac
14_MS_butler.avi
...and so on...
使用相同的原则,可以创建一个简单的命令,以便并行组合文件:
$ ls -1 | parallel --max-args=2 ffmpeg -i {1} -i {2} -vcodec copy -acodec copy {1}.mkv
蛮力。
所有这些花哨的输入和输出解析并不适合每个人的口味。 如果您喜欢更直接的方法,您可以向 Parallel 扔命令然后走开。
首先,创建一个文本文件,每行一个命令:
$ cat jobs2run
bzip2 oldstuff.tar
oggenc music.flac
opusenc ambiance.wav
convert bigfile.tiff small.jpeg
ffmepg -i foo.avi -v:b 12000k foo.mp4
xsltproc --output build/tmp.fo style/dm.xsl src/tmp.xml
bzip2 archive.tar
然后将文件交给 Parallel:
$ parallel --jobs 6 < jobs2run
现在您文件中的所有任务都在 Parallel 中运行。 如果存在的任务多于允许的任务,则 Parallel 会形成并维护一个队列,直到所有任务都运行完毕。
更多,更多
GNU Parallel 是一个强大而灵活的工具,其用例远不止本文所能容纳的。 它的手册页提供了非常酷的事情的例子,您可以从中了解到,从通过 SSH 进行远程执行到将 Bash 函数合并到您的 Parallel 命令中。 甚至还有一个广泛的演示系列在 YouTube 上,因此您可以直接从 GNU Parallel 团队学习。 GNU Parallel 的主要维护者也刚刚发布了该命令的官方指南,可从 Lulu.com 获得。
GNU Parallel 有能力改变您的计算方式,如果它没有做到这一点,它至少会改变您的计算机花费在计算上的时间。 今天就试试吧!
9 条评论