您是否曾经觉得您的电脑没有达到它应有的速度?我过去常常有这种感觉,然后我发现了 GNU Parallel。
GNU Parallel 是一个用于并行执行作业的 shell 实用程序。它可以解析多个输入,从而同时针对多组数据运行您的脚本或命令。您终于可以使用所有的 CPU 了!
如果您曾经使用过 xargs
,您就已经知道如何使用 Parallel。如果您没有,那么这篇文章将教您,以及许多其他的用例。
安装 GNU Parallel
GNU Parallel 可能没有预装在您的 Linux 或 BSD 电脑上。从您的软件仓库或端口集合中安装它。例如,在 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 分钟的时间,使用 Parallel 可能只需要 5 分钟或 3 分钟。
多个输入
只要您熟悉 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 会形成并维护一个队列,直到所有作业都运行完毕。
更多,更多
GNU Parallel 是一个强大而灵活的工具,它的用例远远超出本文的范围。它的手册页提供了您可以利用它做出的非常酷的事情的例子,从通过 SSH 进行远程执行到将 Bash 函数合并到您的 Parallel 命令中。甚至还有一个广泛的演示系列在 YouTube 上,因此您可以直接从 GNU Parallel 团队那里学习。GNU Parallel 的主要维护者也刚刚发布了该命令的官方指南,可从 Lulu.com 获取。
GNU Parallel 有能力改变您计算的方式,如果它没有做到这一点,它至少会改变您的计算机花费在计算上的时间。今天就试试吧!
9 条评论