【问题标题】:How to split files up and process them in parallel and then stitch them back? unix如何拆分文件并并行处理它们然后将它们缝合回去? Unix
【发布时间】:2015-05-15 23:33:09
【问题描述】:

我有一个文本文件infile.txt

abc what's the foo bar.
foobar hello world, hhaha cluster spatio something something.
xyz trying to do this in parallel
kmeans you're mean, who's mean?

文件中的每一行都会被这个perl命令处理成out.txt

`cat infile.txt | perl dosomething > out.txt`

想象一下,如果文本文件是 100,000,000 行。我想并行化 bash 命令,所以我尝试了这样的操作:

$ mkdir splitfiles
$ mkdir splitfiles_processed
$ cd splitfiles
$ split -n3 ../infile.txt
$ for i in $(ls); do "cat $i | perl dosomething > ../splitfiles_processed/$i &"; done
$ wait
$ cd ../splitfiles_processed
$ cat * > ../infile_processed.txt

但是有没有更简洁的方法来做同样的事情?

【问题讨论】:

  • 为什么还要拆分呢?还有一个cat 滥用。 perl可以就地修改,也可以做备份。
  • 听起来像是 XY 问题。您真正想要解决的问题是什么导致您想要使用这种类型的解决方案?
  • cat ... | ... 视为# do something。我需要拆分一个文件以分段处理它,然后将处理后的部分缝合回一个文件。我无法控制#do something,所以我给它一个文本文件,它输出一个处理过的文本文件。
  • 您有一个可行的解决方案。您可能找不到更简单的 MAP/REDUCE 实现。八行 shell 代码并不是一个冗长的解决方案。

标签: bash perl unix split cat


【解决方案1】:

@Ulfalizer 的回答为您提供了有关解决方案的良好提示,但缺少一些细节。

您可以使用GNU parallel(Debian 上为apt-get install parallel

所以你的问题可以使用以下命令解决:

parallel -a infile.txt -l 1000 -j 10 -k --spreadstdin perl dosomething > result.txt

以下是参数的含义:

-a: read input from file instead of stdin
-l 1000: send 1000 lines blocks to command
-j 10: launch 10 jobs in parallel
-k: keep sequence of output
--spreadstdin: sends the above 1000 line block to the stdin of the command

【讨论】:

  • 感谢亚当!为了分散积分,如果没问题,复选标记会交给你,@Ulfalizer 会得到赏金 =)
  • 真棒平行
【解决方案2】:

我自己从未尝试过,但GNU parallel 可能值得一试。

这是手册页 (parallel(1)) 的摘录,与您当前正在执行的操作类似。它也可以通过其他方式拆分输入。

示例:使用更多内核处理大文件 要处理大文件或某些输出,您可以使用 --pipe 拆分 将数据放入块并将这些块通过管道传输到处理程序中。 如果程序是 gzip -9 你可以这样做: 猫大文件 |并行 --pipe --recend '' -k gzip -9 >bigfile.gz 这会将大文件拆分为 1 MB 的块并将其传递给 gzip -9 在平行下。每个 CPU 核心将运行一个 gzip。 gzip -9 的输出 将保持有序并保存到 bigfile.gz

这是否值得取决于您的处理对 CPU 的密集程度。对于简单的脚本,您将花费大部分时间在磁盘之间进行数据混洗,而并行化不会给您带来太多好处。

您可以找到 GNU Parallel 作者here 的一些介绍视频。

【讨论】:

    【解决方案3】:

    假设您的限制因素不是您的磁盘,您可以在 perl 中使用fork() 尤其是Parallel::ForkManager 执行此操作:

    #!/usr/bin/perl
    
    use strict;
    use warnings;
    
    use Parallel::ForkManager;
    
    my $max_forks = 8; #2x procs is usually optimal
    
    sub process_line {
        #do something with this line
    }
    
    my $fork_manager = Parallel::ForkManager -> new ( $max_forks ); 
    
    open ( my $input, '<', 'infile.txt' ) or die $!;
    while ( my $line = <$input> ) {
        $fork_manager -> start and next;
        process_line ( $line );
        $fork_manager -> finish;
    }
    
    close ( $input );
    $fork_manager -> wait_all_children();
    

    这样做的缺点是合并输出。每个并行任务不一定按其开始的顺序完成,因此在序列化结果方面存在各种潜在问题。

    您可以使用flock 之类的方法来解决这些问题,但您需要小心,因为过多的锁定操作首先会剥夺您的并行优势。 (因此我的第一个陈述 - 如果您的限制因素是磁盘 IO,那么并行性无论如何都没有多大帮助)。

    虽然有各种可能的解决方案 - 在 perl 文档中写了整整一章:perlipc - 但请记住,您也可以使用 Parallel::ForkManager 检索数据。

    【讨论】:

    • 我喜欢Parallel::ForkManager,但不是一次处理一行。处理文件的一个分片 - 是的,一行 - 不,太多的分叉或开销。
    • 它正在复制 OP 的请求。但是,是的,它宁愿取决于每条线路需要多少“努力”。 fork 虽然相当轻量级 - 并且将会发生在例如任何类型的 gnu 并行样式操作。
    • Fork 不是轻量级的。它是最重的。
    • 最重的是什么?您调用的每个进程都是一个 fork 和 exec。因此,它在 Unix 上得到了很好的优化。
    • 每个系统已经存在了几十年,它们仍然有相对的成本,而 fork/exec 是最高的,这就是为什么在优化 web 堆栈时首先要消除它。
    猜你喜欢
    • 2021-07-06
    • 2020-01-20
    • 1970-01-01
    • 2019-09-20
    • 1970-01-01
    • 2017-08-13
    • 1970-01-01
    • 2022-01-14
    • 2018-05-01
    相关资源
    最近更新 更多