带有Xargs的并行Shell

2021-02-19 00:30:08

UNIX shell的一个特别令人沮丧的地方是无法轻松地调度多个并发任务,这些任务无法充分利用现代系统上提供的CPU内核。本文重点关注的示例是文件压缩,但是问题出在许多计算密集型任务上,例如图像/音频/媒体处理,密码破解和哈希分析,数据库提取,转换和加载以及备份活动。等待gzip *在单个CPU内核上运行,而大多数计算机的处理能力却处于闲置状态,这令人感到沮丧。

可以将其理解为Research UNIX最初十年的弱点,而这不是在具有SMP的计算机上开发的。从第7版开始,Bourne shell并没有使用任何本机语法或控件来统一管理后台进程的资源消耗。

实用程序已经随意发展以执行其中一些功能。 xargs的GNU版本能够在分配后台进程中执行一些原始控制,在文档中对此进行了详细讨论。尽管xargs的GNU扩展已经扩展到许多其他实现(尤其是BusyBox,包括下面的示例中的Microsoft Windows版本),但它们不符合POSIX.2,并且可能在商业UNIX上找不到。

xargs的历史用户会记住它是一个有用的工具,用于包含太多文件的目录,无法使用echo *或其他通配符;在这种情况下,将调用xargs以单个命令重复批处理文件组。随着xargs超越POSIX的发展,它假定了一个新的相关性,这对探索很有用。

要清楚地了解UNIX中缺乏内聚的作业调度,需要了解这些实用程序的发展历史。

POSIX.2定义的外壳具有原始作业控制功能。此功能起源于一个源,该源由Bill Joy编写,并于1978年首次发布,从那时起,即使在Korn shell吸收了作业控制之后,它也没有取得显着进步。以下是在bash中实现的[c] sh作业管理的示例,POSIX.2 shell仍受其约束。在此会话中,^ Z和^ C表示控制键组合。

$ xz -9e users00.dat ^ Z [1] +已停止xz -9e users00.dat $ bg [1] + xz -9e users00.dat&$ xz -9e users01.dat ^ Z [2] +已停止xz- 9e users01.dat $ xz -9e users02.dat ^ Z [3] +已停止xz -9e users02.dat $作业[1]运行xz -9e users00.dat& [2]-已停止xz -9e users01.dat [ 3] +停止xz -9e users02.dat $ bg 3 [3] + xz -9e users02.dat&$ jobs [1]运行xz -9e users00.dat& [2] +停止xz -9e users01.dat [3]-运行xz -9e users02.dat& $ fg 2xz -9e users01.dat ^ C $作业[1]-运行xz -9e users00.dat& [3] +运行xz -9e users00.dat& ;

在上面的示例中,已经启动了三个压缩命令,第二个已取消,其余命令推到后台。

当资源可用时,没有报告或分配可用的CPU来占用作业。

返回非零退出状态或以其他方式异常终止的失败命令无法很好地传达。将此类情况放置在失败的队列中以重新运行将很有帮助。

没有可用的全局系统作业调度。任何用户都可以独自或与其他人一起发出使计算机不堪重负的后台作业。

尽管SMP最初出现在1962年上市的计算机系统中,并在UNIX诞生的同一年随IBM System / 370的发布而牢固地建立起来,但功能强大的机器在"中却不为开发人员所用。贫穷"所谓的Research UNIX。具有这些功能的系统通常不会持续多年。

UNIX系统不支持多处理... IBM 3033AP处理器以单个PDP-11 / 70处理器的大约15倍的计算能力满足了这一要求。

似乎第一个具有SMP功能的UNIX平台是Sperry / UNIVAC 1100,这是一个内部AT& T端口,始于1977年。此端口以及后来的IBM在System / 370上的努力均基于供应商提供的OS组件。 (EXEC 8和TSS),并且似乎不依赖于在第7版内核中实现的常规SMP。

由于csh不能在多处理机上编写,并且在UNIX System V之前的那几年通常没有引入SMP,因此shell作业控制同样没有多个处理器的可见性,并且没有设计成可以利用它们。

由于UNIX战争,这种缺乏进展在POSIX.2中得到了巩固,在这些战争中,由IBM,HP和DEC(及其他)领导的财团作为防御措施发布了这些标准,从而将UNIX System V功能锁定在业界整天。对于许多人来说,不允许超越POSIX进行创新。

当POSIX.2获得批准时,所有主要参与者都实施了SMP,但是没有发现将POSIX.2标准外壳扩展到System V之外的动力。这使x86服务器NUMA和嵌入式big.LITTLE在任何严格遵循的标准中均被低估了。 POSIX实现。

并行发布gzip进程仍然是一项艰巨的任务,其原因是由于编纂了防御性营销。

由于POSIX.2 Shell中缺少现代的作业控制,因此可以利用一种hack在GNU xargs中提供扩展的功能。其他解决方案包括GNU parallel和pdsh,此处未介绍。

经典的xargs实用程序将标准输入和位置参数组合到fork命令。一个简单的xargs示例可能是列出一些inode编号:

当处理大量超过Shell命令行最大大小的文件时,此基本调用非常有用。下面是来自xargs的古老商业UNIX的示例,用于解决外壳程序内存故障:

$ uname -aHP-UX本地主机B.10.20 A 9000/800 862741461两用户许可证$ cd / directory / with / lots / of / files $ chmod 644 * sh:现在没有足够的可用内存。 xargs chmod 644 $ echo * sh:现在没有足够的可用内存。$ ksh $什么/ usr / bin / ksh | grep版本版本11/16/88 $ echo * ksh:无空格$ / usr / dt / bin / dtksh $ echo $ {。sh.version}版本M-12 / 28 / 93d $ echo * Pid 1954收到了SIGSEGV堆栈增长失败。可能的原因:内存或交换空间不足,或者堆栈大小超过了maxssiz。内存故障$ / usr / old / bin / sh $ ls * / usr / bin / ls:arg list too long $ ls * *没有堆栈空间

POSIX xargs存在一个问题,因为它不能很好地处理标准输入中文件中的空格或换行符。 UNIX文件名中唯一被普遍禁止的字符是正斜杠(/)。 GNU扩展-0参数将文件定界符设置为NUL或零字节值,这大大简化了文件处理,并大大提高了安全性。 GNU find具有在管道中利用此功能的开关。实际上,缺少-x的xargs不值得使用。

第二个主要的GNU扩展允许使用-P#参数进行并行处理。就其本身而言,这不会触发并行处理,但是当与-L 1选项结合使用时,所有输入文件将与目标程序分开启动,仅运行分配的进程插槽数。

在启动我们的第一个并行脚本之前,请验证此程序,该程序报告Linux可见的处理器CPU内核数:

此数字可能不反映物理核心,但也可能反映每个核心可以多个实现的SMT /超线程。在单个内核上实现的线程中运行时,某些命令不能很好地运行。

现在,我们提供一个并行压缩脚本,该脚本足够灵活以生成几种文件格式。它符合POSIX,并在Debian DASH和BusyBox shell下运行。

$ cat〜/ ppack_lz#!/ bin / sh PARALLEL =" $(nproc --ignore = 1)" EXT =" $ {0 ## * _}"案例" $ EXT"在bz2中)CMD =' bzip2 -9' ;; gz)CMD =' gzip -9' ;; lz)CMD =' lzip -9' ;; xz)CMD =' xz -9e' ;; zst)CMD =' zstd --rm-单线程--ultra -22' ;; esac if [-z" $ 1" ]然后回显"指定要打包到$ {EXT}文件中的文件。"否则为x做printf'%s \ 0' " $ x"完成|漂亮的xargs -0 -L 1 -P" $ PARALLEL" $ CMD fi

该脚本配置为使用除nproc报告的CPU之外的所有CPU。根据机器负载,最好手动设置。

该脚本检测脚本文件名中下划线(_)后的最后一个字符所执行的压缩类型。如果脚本名为foo_bz2,则它将执行bzip2处理,而不是ppack_lz上面选择的lzip。

指定为脚本参数的要压缩文件将由for循环在其标准输出(由NUL分隔)上由for循环发出,并由xargs进行调度。

要观察该脚本的运行情况,拥有一个(几乎与POSIX兼容)的shell函数来搜索ps命令的输出是有帮助的:

psearch(){本地xx_a xx_b xx_COLUMNS IFS = \ | [-z" $ COLUMNS" ]&& xx_COLUMNS = 80 || xx_COLUMNS =" $ COLUMNS" ps -e -o user:7,pid:5,ppid:5,start,bsdtime,%cpu,%mem,args |而在读取xx_a时,如果[-z" $ xx_b" ]然后打印f'%s \ n' " $ {xx_b:= $ xx_a}"否则为xx_b做大小写" $ xx_a"在*" $ xx_b" *)中printf'%s \ n' " $(expr substr" $ xx_a" 1" $ xx_COLUMNS")" ;; esac完成,fi完成}

准备好该监视器后,我们可以使用四核CPU在几个WAV文件上运行此脚本:

$ psearch lzip用户PID PPID启动时间%CPU%MEM命令cfisher 29995 29992 16:01:49 0:00 0.0 0.0 xargs -0 -L 1 -P 3 lzip -9 cfisher 30007 29995 16:02:10 0:27 100 2.8 lzip -9 track01.cdda.wav cfisher 30046 29995 16:02:31 0:05 97.5 1.4 lzip -9 track02.cdda.wav cfisher 30049 29995 16:02:33 0:04 108 1.2 lzip -9 track03.cdda波形

如xargs并行性文档中概述的那样,发送SIGUSER1和SIGUSER2将相反地增加和减少xargs调度的并行进程的数量。添加立即生效,而减少将等待现有流程退出。

上面xargs命令的形式是受约束的,因为预定参数和xargs提供的参数的顺序无法调整。可以使用POSIX -I选项设置更细微的版本,以提供更大的脚本编写灵活性,但是它需要" meta脚本"。在运行时生成的。

$ cat〜/ parallel-pack_gz#!/ bin / sh PARALLEL =" $(nproc --ignore = 1)" S =" $(mktemp -t PARALLEL-XXXXXX)"陷阱' f" $ S"'退出EXT =" $ {0 ## * _}"案例" $ EXT"在7z中)printf' / bin / sh \ n exec 7za a -bso0 -bsp0 --mx = 9" $ {1} .7z" " $ 1"' ;; bz2)printf' / bin / sh \ n exec bzip2 -9" $ 1"' ;; gz)printf' / bin / sh \ n exec gzip -9" $ 1"' ;; lz)printf' / bin / sh \ n exec lzip -9" $ 1"' ;; xz)printf' / bin / sh \ n exec xz -9e" $ 1"' ;; zst)printf' / bin / sh \ nexec zstd --rm-单线程--ultra -22" $ 1"&#39 ;; esac> " $ S" chmod 500" $ S"如果[-z" $ 1" ]然后回显"指定要打包到$ {EXT}文件中的文件。"否则为x做printf'%s \ 0' " $ x"完成|不错的xargs -0 -P" $ PARALLEL" -Ifname" $ S" fname fi

上面添加了对7za的调用,该调用包含在许多平台上可用的p7zip软件包中(Red Hat用户可以在EPEL中找到它)。 7-zip的使用带有一些警告,因为程序本身是多线程的(使用1.25-1.5个内核),并且内存需求增加了,因此应减少并行进程的数量。此外,7-zip可以附加到现有存档(例如Info-ZIP,打算将其替换)。不要安排将多个7-zip进程追加到同一目标文件。 7-zip的加密选项在避免备份媒体上的安全漏洞规定方面可能特别有用。

这篇文章的标题是“平行壳”,在上面的用法上在技术上是正确的,上面的exec立即擦除了shell,并且更有效地使用了进程表。

有了这个灵活的脚本,我们就对多线程gzip Pigz进行了基准测试,对80个2 GB的文件(在本例中是Oracle数据库数据文件,随机包含表和索引块)进行了基准测试。基本服务器是(较旧的)HP DL380 Gen8,具有8个可用的处理器核心:

$ lscpu | grep名称型号名称:Intel(R)Xeon(R)CPU E5-2609 0 @ 2.40GHz#time pigz -9v users * users00.dat到users00.dat.gzusers01.dat到users01.dat.gzusers02.dat到users02.dat .gz ... users77.dat到users77.dat.gzusers78.dat到users78.dat.gzusers79.dat到users79.dat.gzreal 45m51.904suser 335m15.939ssys 2m11.146s

PID用户PR NI VIRT RES SHR S%CPU%MEM TIME + COMMAND11162根20 0 617616 6864 772 S 714.2 0.0 17:58.21 Pigz -9v users01.dat ...

在此(理想)基准测试中,即使在相同的主机上将PARALLEL设置为8,xargs脚本也稍快一些,甚至可以在CPU优先级下运行:

在运行xargs协调的并行gzip的过程中,最上面的报告列出了在单独的CPU上调度的所有单线程进程(请注意,优先级30,降低了nice,与之相比,pigz为20):

PID用户PR NI VIRT RES SHR S%CPU%MEM TIME + COMMAND14624根30 10 4624 828 424 R 100.0 0.0 0:09.85 gzip -9 users00.dat14625根30 10 4624 832 424 R 100.0 0.0 0:09.86 gzip -9 users01.dat ... 14630根30 10 4624 832 424 R 99.3 0.0 0:09.76 gzip -9 users06.dat14631根30 10 4624 824 424 R 98.0 0.0 0:09.69 gzip -9 users07.dat

在这种理想情况下,文件数可以平均除以CPU数,这有助于并行xargs击败Pigz。添加另一个文件将导致xargs失去这场比赛。

也有bzip2(pbzip2),lzip(plzip),xz(pixz)的并行版本,并且zstd实用程序通常是多线程的,并且将利用所有cpu内核,但是上面禁用了此默认设置。多线程版本可能显示出与xargs不同的性能特征。对于7za,xargs是提高计算机利用率的明显方法。

在旋转媒体上并行xargs调度的一个重要I / O问题是碎片。尽管这不是SSD的一个因素,但这是常规存储中的一个问题,如果可能的话,应定期解决此问题,从结果可以看出,该问题与inode数匹配:

#ls -li users46.dat.lz 2684590096 -rw-r--r--。 1 oracle dba 174653599 1月28日13:30 users46.dat.lz#xfs_fsr -v ... ino = 2684590096范围之前:52之后:1完成ino = 2684590096 ...

XFS文件系统(Red Hat及其衍生产品的本机)上的碎片很明显,应注意定期解决存在工具进行补救的文件系统上的碎片(即e4defrag,btrfs碎片整理)。在不存在用于解决碎片问题的工具的ZFS文件系统上,应格外小心地进行并行处理,并且只能对位于池中并保持足够的可用空间的数据集进行处理。

由于存在碎片问题,我们放弃了并行解压缩,但是更喜欢单线程,与压缩无关的方法:

$ cat unpack#!/ bin / sh for x做回声" $ x" EXT =" $ {x ## *。}"案例" $ EXT"在bz2中)bzip2 -cd" $ x" ;; gz)gzip -cd" $ x" ;; lz)lzip -cd" $ x" ;; xz)xz -cd" $ x" ;; zst)zstd -cd" $ x" ;; esac> " $(基本名称" $ x""。$ {EXT}")"完毕

最后,此技术可以在Windows的BusyBox端口上使用,并且可能在支持GNU xargs的Win32 / 64平台上的其他(POSIX)Shell实现中使用。 BusyBox外壳程序没有实现nice(将其从脚本中删除),nproc也不存在(手动设置为PARALLEL)。 BusyBox仅完全实现gzip和bzip2(存在xz applet,但不实现数字质量设置)。调整bzip2的更改,这是我的笔记本电脑上的演示,其中测试了所有Cygwin .DLL文件的副本:

C:\ Temp> busybox64 sh C:/ Temp $ time sh parallel-pack_bz2 dtest / *。dll实0m 58.70s用户0m 0.00s sys 0m 0.06s C:/ Temp $ exit C:\ Temp> dir dtest C是OSDisk卷序列号是E44B-22EC C:\ Temp \ dtest 02/02/2021目录11:10 AM< DIR> 。 20/02/02 21上午11:10< DIR> ..02 / 02/2021 11:09 AM 40,957 cygaa-1.dll.bz2 02/02/2021 11:09 AM 263,248 cygakonadi-calendar-4.dll.bz2 02/02/2021 11:09 AM 289,716 cygakonadi- contact-4.dll.bz2。 。 。 02/02/2021 11:10 AM 658,119 libtcl8.6.dll.bz2 02/02/2021 11:10 AM 489,135 libtk8.6.dll.bz2 02/02/2021 11:09 AM 5,942 Xdummy.dll.bz2 1044文件338,341,460字节2目录133,704,908,800字节免费

UNIX ...是唯一可以在从单片微型计算机到最大的通用大型机等所有设备上运行的操作系统...这表示功率和容量至少在两个数量级范围内。 UNIX系统能够很好地涵盖从微型计算机到高端大型机的各种功能,这是对它在十多年前的最初设计及其精心发展的贡献。

同时,我们对我们不了解的作业控制(在System / 370操作系统下)怀有怀旧之情。

尽管Linux可能无法达到PDP-11的最低要求,但它在很大程度上与第七版具有相同的特性,并且从1970年代的角度来看,它可以在无法想象的速度下运行。但是,POSIX.2要求我们在1970年代仍然使用多种工具,可能会通过更好的(作业)工具将用户吸引到规模较小的竞争对手中。

我在90年代初期在大学的Encore Multimax上开始自己对UNIX SMP的接触,并且无法想象甚至POSIX.2的不合理要求也限制了该计算机的用户领域。即使在现在,要接受对现代SMP设计的相同限制在一定程度上也是令人厌恶的。

POSIX在许多领域被认为是一种苛刻的标准。看到它被SELinux和systemd小幅超越,提供了一些希望,我们可以克服上一代产品给我们带来的限制。也许显而易见的解决方案将涉及systemd获取新的作业调度系统。尽管可能会争辩说可移植性否决了功能,但创新也最终必定会否决传统。便携性是有用的追求,但是功能和效率也不是没有价值的。

并非严格要求内核参与改进的作业计划系统。添加到POSIX的基本用户空间实现很可能会受到用户社区的欢迎(希望在运行时调整方面比SIGUSR1 / 2更好)。 POSIX不允许这样做,但是现在该丢掉过去了。

由于UNIX的早期贫乏,被迫成为晦涩的实用程序进行并行脚本编写是不合理的。对于功能强大的shell和杂项用户区实用程序而言,更新的POSIX.2标准早就该发布了。