如何拆分文件并并行处理它们然后将它们缝合回去? Unix

Posted

技术标签:

【中文标题】如何拆分文件并并行处理它们然后将它们缝合回去? Unix【英文标题】:How to split files up and process them in parallel and then stitch them back? 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 代码并不是一个冗长的解决方案。 【参考方案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 堆栈时首先要消除它。

以上是关于如何拆分文件并并行处理它们然后将它们缝合回去? Unix的主要内容,如果未能解决你的问题,请参考以下文章

linux shell脚本:拆分字符串,将它们放在一个数组中然后循环它们[重复]

在 GCP 中按行拆分大文件

如何拆分文本文件并在一行中存储 2 个值?

将 txt 文件合并为一个文件,然后再次拆分它们

(java)如何在零件上拆分一个数字,然后将它们相互比较

拆分和重新加入 wav 文件