高效处理目录中 500,000 个小文件的 Perl 程序

Perl Program to efficiently process 500,000 small files in a directory

我每晚都在处理一个大目录。它每晚累积大约 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);

试试这样的:

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{} }, $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 @_;
}

这会将工作分成三个主要部分,您可以在其中为每个部分计时,找出花费最多时间的地方。

您正在为单个目录中的大量文件所困扰。

我创建了 80_000 个文件和 运行 你的脚本,它们在 5.2 秒内完成。这是在装有 CentOS7 和 v5.16 的旧笔记本电脑上。但是对于一百万个文件,它需要将近 7 分钟。因此,问题不在于代码本身的性能(但也可以收紧)。

然后一个解决方案很简单:运行 cron 中的脚本,比如说每小时一次,因为文件即将到来。当您移动 .txt 文件时,也会将其他文件移动到其他地方,并且永远不会有太多文件;该脚本将始终 运行 秒。最后,如果需要,您可以将其他文件移回原处。

另一种选择是将这些文件存储在具有不同文件系统的分区上,比如 ReiserFS。但是,这根本没有解决目录中文件太多的主要问题。

另一个部分修复是替换

while ( defined( my $txtFile = readdir DIR ) )

while ( my $path = <"$dir/*txt"> )

结果是 1m:12s 运行(而不是将近 7 分钟)。不要忘记调整 file-naming,因为 <> 高于 returns 文件的完整路径。同样,这并不能真正解决问题。

如果您可以控制文件的分布方式,您将需要一个 3 级(或左右)的深层目录结构,可以使用文件的 MD5 命名,这将导致非常均衡的分布。


文件名及其内容创建为

perl -MPath::Tiny -wE'
    path("dir/s".$_.".txt")->spew("A|some_id|$_\n") for 1..500_000
'

这是我经常执行的任务。其中一些已经在各种评论中提到。 None 其中对 Perl 来说是特殊的,最大的胜利将来自改变环境而不是语言。

  • 将文件分段到单独的目录中以保持目录较小。较大的目录需要更长的时间来读取(有时呈指数级增长)。这发生在任何生成文件的地方。文件路径类似于 .../ab/cd/ef/filename.txt 其中 ab/cd/ef 来自某个函数不太可能发生碰撞。或者可能像 .../2018/04/01/filename.txt.

  • 您可能对制作人没有太多控制权。我会调查让它向单个文件添加行。其他东西会在以后制作单独的文件。

  • 运行 更频繁并将处理过的文件移动到其他地方(同样,可能使用散列。

  • 运行 持续并定期轮询目录以检查新文件。

  • 运行程序并行。如果您有很多空闲内核,请让它们继续工作。你需要一些东西来决定谁开始做什么。

  • 与其创建文件,不如将它们推送到轻型数据存储中,例如 Redis。或者也许是重量级数据存储。

  • 不要实际读取文件内容。请改用 File::Mmap。这对于非常大的文件来说通常是一个胜利,但我没有在大量的小文件上玩过它。

  • 获得更快的旋转磁盘或 SSD。不幸的是,我不小心在慢速磁盘上的一个目录中创建了数百万个文件。

我认为没有人提出来,但您是否考虑过 运行 一个使用文件系统通知作为近实时事件而不是批量处理的长期过程?我确定 CPAN 会为 Perl 5 提供一些东西,在 Perl 6 中有一个内置对象来说明我的意思 https://docs.perl6.org/type/IO::Notification 也许其他人可以插话什么是 P5 中使用的好模块?