我每晚都在处理一个大目录。它每晚累积大约100万个文件,其中一半是我需要根据文件内容移动到不同目录的.txt
个文件。
每个.txt
文件都是以竖线分隔的,只包含20条记录。记录6是包含确定将文件移动到哪个目录所需的信息的记录。
示例记录:
A|CHNL_ID|4
在这种情况下,文件将移至/out/4
。
此脚本的处理速度为每小时80,000个文件。
关于如何加快速度,是否有任何建议?
opendir(DIR, $dir) or die "$!\n";
while ( defined( my $txtFile = readdir DIR ) ) {
next if( $txtFile !~ /.txt$/ );
$cnt++;
local $/;
open my $fh, '<', $txtFile or die $!, $/;
my $data = <$fh>;
my ($channel) = $data =~ /A\|CHNL_ID\|(\d+)/i;
close($fh);
move ($txtFile, "$outDir/$channel") or die $!, $/;
}
closedir(DIR);
答案 0 :(得分:5)
您受到单个目录中大量文件的伤害。
我创建了80_000
个文件并运行了在5.2秒内完成的脚本。这是在装有CentOS7和v5.16的旧笔记本电脑上。但是有50万个文件†需要将近7分钟。因此问题不在于代码本身的性能(但也可以加强)。
然后一个解决方案很简单:在文件即将到来时,每隔一小时运行一个cron脚本。当您移动.txt
文件时,也会移动其他文件,并且永远不会有太多文件;该脚本将始终在几秒钟内运行。最后,如果需要,您可以将其他文件移回。
另一种选择是将这些文件存储在具有不同文件系统的分区上,例如ReiserFS。但是,这并没有解决在目录中包含太多文件的主要问题。
另一个部分修复是替换
while ( defined( my $txtFile = readdir DIR ) )
与
while ( my $f = <"$dir/*txt"> )
导致1m:12s运行(而不是接近7分钟)。不要忘记调整文件命名,因为<>
返回完整路径。同样,这并没有真正解决这个问题。
如果您可以控制文件的分发方式,那么您需要一个3级(左右)深层目录结构,可以使用文件命名。 MD5,这将导致非常均衡的分布。
†文件名及其内容创建为
perl -MPath::Tiny -wE'
path("dir/s".$_.".txt")->spew("A|some_id|$_\n") for 1..500_000
'
答案 1 :(得分:3)
这是我经常执行的任务。其中一些已在各种评论中提及。这些对Perl来说都不是特别的,最大的胜利来自改变环境而不是语言。
将文件分段到单独的目录中以使目录保持较小。较大的目录需要更长的时间来阅读(有时是指数级)。这会发生在生成文件的任何内容中。文件路径类似于 ... / ab / cd / ef / filename.txt ,其中 ab / cd / ef 来自一些不太可能发生冲突的函数。或者它可能像 ... / 2018/04/01 / filename.txt 。
您可能无法控制制片人。我正在调查它是否将行添加到单个文件中。其他东西会在以后制作单独的文件。
更频繁地运行并将处理过的文件移到其他地方(同样可能使用散列。
持续运行并定期轮询目录以检查新文件。
并行运行程序。如果你有很多闲置核心,那就让他们一起工作吧。你需要一些东西来决定谁去做什么。
不是创建文件,而是将它们推送到轻量级数据存储中,例如Redis。或者也许是重量级数据存储。
实际上并未阅读文件内容。请改用File :: Mmap。对于非常大的文件来说,这通常是一个胜利,但是在大量的小文件集中,我还没有玩过很多文件。
获得更快的旋转磁盘或SSD。我不幸在一个慢速磁盘上的一个目录中意外创建了数百万个文件。
答案 2 :(得分:2)
尝试类似:
print localtime()."\n"; #to find where time is spent
opendir(DIR, $dir) or die "$!\n";
my @txtFiles = map "$dir/$_", grep /\.txt$/, readdir DIR;
closedir(DIR);
print localtime()."\n";
my %fileGroup;
for my $txtFile (@txtFiles){
# local $/ = "\n"; #\n or other record separator
open my $fh, '<', $txtFile or die $!;
local $_ = join("", map {<$fh>} 1..6); #read 6 records, not whole file
close($fh);
push @{ $fileGroup{$1} }, $txtFile
if /A\|CHNL_ID\|(\d+)/i or die "No channel found in $_";
}
for my $channel (sort keys %fileGroup){
moveGroup( @{ $fileGroup{$channel} }, "$outDir/$channel" );
}
print localtime()." finito\n";
sub moveGroup {
my $dir=pop@_;
print localtime()." <- start $dir\n";
move($_, $dir) for @_; #or something else if each move spawns sub process
#rename($_,$dir) for @_;
}
这会将作业分成三个主要部分,您可以为每个部分计算时间,以找出花费大部分时间的地方。
答案 3 :(得分:2)
我认为没有人提出这个问题,但您是否考虑过使用文件系统通知作为近实时事件而不是批量处理的长期流程?我确定CPAN会为Perl 5提供一些内容,Perl 6中有一个内置对象,用于说明我的意思https://docs.perl6.org/type/IO::Notification也许其他人可以在P5中使用什么样的好模块?
答案 4 :(得分:-1)
我会将目录列表加载到内存中,然后浏览我的副本,而不是在文件移出它的目录中重复调用readdir。我不确切知道&#34;目录对象&#34;在它们被打开后响应目录更改 - 可能Perl在开放时已经获取了dir的快照,但是它再次可能使用文件系统提供的目录对象它自己的迭代器 - 所以明确地将所有名称都读成@array可能会有所帮助并且不会受到伤害。