易于并行化

6

我经常发现自己写简单的for循环来对许多文件执行操作,例如:

for i in `find . | grep ".xml$"`; do bzip2 $i; done

在我的4核机器上,只有一个核心在使用,这让我感到有点沮丧.. 有没有简单的方法可以为我的shell脚本添加并行性呢?
编辑:为了更好地解释问题,抱歉我一开始没有表述得更清楚!
我经常想要运行一些简单的脚本,比如绘制图形、压缩或解压缩,或者运行一些程序,处理中等大小的数据集(通常在100到10,000之间)。我用来解决此类问题的脚本看起来像上面那个示例,但可能具有不同的命令,甚至是一系列要执行的命令。例如,现在我正在运行:
for i in `find . | grep ".xml.bz2$"`; do find_graph -build_graph $i.graph $i; done

因此,我的问题与bzip无关!(虽然并行bzip看起来很酷,但我打算在未来使用它)。

只是提醒一下,你可以使用xargs而不必编写这样的循环:find . | grep ".xml.bz2$" | xargs -n1 bzip2(-n1表示仅将每个bzip传递1个参数,默认情况下,xargs会将所有参数传递给单个进程)。不幸的是,xargs会逐个处理每个进程。 - Evan Teran
你应该使用 find . -name \*.xml.bz2 而不是 find . | grep ".xml.bz2$" - 这正是 find 命令的用途!(另外,你的正则表达式会匹配文件名如 foozxmlzbz,但这是一个不同且不重要的问题)。 - Adam Rosenfield
等一下,Evan,xargs有一个标记为“-P”的参数,用于指定进程数量!所以: find . | grep ".xml.bz2$" | xargs -n1 -P3 bzip2 可以实现我想要的功能。xargs 有这个功能多久了? - Chris Jefferson
哈哈,我的回答太晚了。你在我之前就已经想出来了 :p - Johannes Schaub - litb
哈哈,我不知道xargs有-P选项!litb,你因为把我的小评论变成了一个扎实的答案而得到了我的投票 :) - Evan Teran
8个回答

14

解决方案: 使用xargs并行运行(不要忘记-n选项!)

find -name \*.xml -print0 | xargs -0 -n 1 -P 3 bzip2

我会给你打勾,因为我相信这是最好的答案 :) - Chris Jefferson

6

通常情况下,如果您有4个处理器,则运行超过4个任务可能会提高性能。第5个及更高的任务可以在其他任务等待I/O时插入。 - sep332
好观点——另一方面,四个进程竞争I/O和缓存行有时会降低整个进程的速度。 - Peter Crabtree

4

GNU Make有一个很好的并行特性(例如-j 5),可以在您的情况下使用。创建一个Makefile。

%.xml.bz2 : %.xml


all: $(patsubt %.xml,%xml.bz2,$(shell find . -name '*.xml') ) 

那么执行一个


nice make -j 5

将'5'替换为某个数字,可能是CPU数量加1。你可能想要使用'nice'命令使其变得友好,这样在您使用机器时,其他人也可以使用。


我本来想建议使用make。但你比我先说了 =) - gnud
@gnud,我很感兴趣知道你会如何编写Makefile(如果与此不同的话)。 - David Nehme

2

一般问题的答案很难确定,因为这取决于您要并行处理的细节。另一方面,对于这个特定的目的,您应该使用pbzip2而不是普通的bzip2(很有可能pbzip2已经安装或至少在您的发行版的存储库中)。详情请参见:http://compression.ca/pbzip2/


2
我认为这种操作是适得其反的。原因是当越多进程同时访问磁盘时,读写时间就会变长,最终结果是需要更长的时间。瓶颈不会是CPU问题,无论你有多少核心。
你有没有试过在同一硬盘驱动器上同时复制两个大文件?通常先复制一个再复制另一个会更快。
我知道这个任务需要一些CPU功率(bzip2是一个要求高的压缩方法),但在选择我们所有技术人员更容易选择的“具有挑战性”的路径之前,请先测量CPU负载。

使用下面的“runN”脚本,如果我运行3个副本,我会得到2倍的加速(在4个副本时,它开始再次变慢),所以看起来值得这样做 :) - Chris Jefferson
好的,这一次“具有挑战性”的道路真的很值得。 - Fernando Miguélez
一些系统比其他系统更好地处理并发磁盘访问(要好得多!)https://dev59.com/FnVD5IYBdhLWcg3wWKPc - timday

2

我曾经在bash中做过类似的事情。虽然并行构建技巧对于一次性的操作可能更快,但以下是在bash中实现类似功能的主要代码部分,您需要根据自己的目的进行修改:

#!/bin/bash

# Replace NNN with the number of loops you want to run through
# and CMD with the command you want to parallel-ize.

set -m

nodes=`grep processor /proc/cpuinfo | wc -l`
job=($(yes 0 | head -n $nodes | tr '\n' ' '))

isin()
{
  local v=$1

  shift 1
  while (( $# > 0 ))
  do
    if [ $v = $1 ]; then return 0; fi
    shift 1
  done
  return 1
}

dowait()
{
  while true
  do
    nj=( $(jobs -p) )
    if (( ${#nj[@]} < nodes ))
    then
      for (( o=0; o<nodes; o++ ))
      do
        if ! isin ${job[$o]} ${nj[*]}; then let job[o]=0; fi
      done
      return;
    fi
    sleep 1
  done
}

let x=0
while (( x < NNN ))
do
  for (( o=0; o<nodes; o++ ))
  do
    if (( job[o] == 0 )); then break; fi
  done

  if (( o == nodes )); then
    dowait;
    continue;
  fi

  CMD &
  let job[o]=$!

  let x++
done

wait

1
如果你今天需要解决这个问题,你可能会使用像GNU Parallel这样的工具(除非有一个专门的并行化工具适用于你的任务,如pbzip2):
find . | grep ".xml$" | parallel bzip2

了解更多信息:


更新的答案,这是现在更好的选择! - Chris Jefferson

1

我认为你可以按照以下方式操作

for i in `find . | grep ".xml$"`; do bzip2 $i&; done

但是这样会立即产生与文件数量相同的进程,不如一次只运行四个进程。


这对于小型任务来说可能还可以,但我要在大约5,000个文件上运行上述命令。我怀疑这会让我的电脑彻底崩溃! :) - Chris Jefferson
它会淹没其他进程,但是Linux调度程序非常擅长确保进程不会完全饿死。问题在于内存使用,因为分页会严重影响性能。 - sep332
我个人喜欢这个答案,因为它不需要安装任何额外的工具。在你需要进行较少文件搜索的情况下,它会很有效。 - Tom Leys

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