Perl 程序高效处理目录中的 500,000 个小文件
Posted
技术标签:
【中文标题】Perl 程序高效处理目录中的 500,000 个小文件【英文标题】:Perl Program to efficiently process 500,000 small files in a directory 【发布时间】:2018-08-26 05:06:24 【问题描述】:我每晚都在处理一个大目录。它每晚累积大约 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);
【问题讨论】:
您可以将正则表达式移出循环并使用qr
对其进行预编译,但这不会为您节省太多。您应该检查瓶颈是什么,它可能是 cpu、内存或磁盘(提示......它可能是磁盘)。
你有一个包含 80000 个文件的目录吗?如果是这样,这本身可能是您的问题,具体取决于您的文件系统。我建议将它们分成一堆文件较少的子目录......如果你有超过 1000 个文件,那可能已经太多了。
另外,如果您将传入的工作拆分到不同的磁盘上(也可能在不同的机器上),您可以并行运行该程序多次,以加快处理速度。
你有什么样的文件系统?您可以查看/etc/fstab
或运行df
命令来查找。
正如其他人所暗示的那样,您有一个 I/O 绑定任务,其性能完全由文件系统决定。如果事实证明您有多个磁盘通道,那么您可能通过流水线获得加速。例如。使用实例编号 I=0,1,...N-1 作为命令行参数给出的 N 个脚本副本。排序readdir
结果并处理文件 I, I+N, I+2N, ... 。另一种可能性是使用一个脚本来确定将每个文件移动到哪里并将其传递给另一个执行移动操作的文件。除了尝试之外,没有办法判断这些是否会导致加速。
【参考方案1】:
尝试类似:
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 @_;
这会将工作分为三个主要部分,您可以在其中对每个部分进行计时,以找出花费最多时间的地方。
【讨论】:
我会试试这个并告诉你。谢谢! 谢谢凯蒂尔!第一:重命名效率更高。第二:我修改了脚本,运行了 1 小时 39 分钟。【参考方案2】:单个目录中的文件数量之多对您造成了伤害。
我创建了80_000
文件并运行了您的脚本,该脚本在 5.2 秒内完成。这是在装有 CentOS7 和 v5.16 的旧笔记本电脑上。但是有 50 万个文件† 需要将近 7 分钟。因此,问题不在于代码本身的性能(但也可以收紧)。
然后一个解决方案很简单:从 cron 中运行脚本,比如每小时运行一次,因为文件即将到来。当您移动 .txt
文件时,也将其他文件移动到其他地方,并且永远不会有太多文件;该脚本将始终在几秒钟内运行。最后,如果需要,您可以将其他文件移回。
另一种选择是将这些文件存储在具有不同文件系统的分区上,例如 ReiserFS。但是,这根本不能解决目录中文件过多的主要问题。
另一个部分修复是替换
while ( defined( my $txtFile = readdir DIR ) )
与
while ( my $path = <"$dir/*txt"> )
这会导致 1m:12s 的运行(而不是接近 7 分钟)。不要忘记调整文件命名,因为上面的<>
返回文件的完整路径。同样,这并不能真正解决问题。
如果您可以控制文件的分发方式,您将需要一个 3 级(左右)的深层目录结构,可以使用文件的 MD5 命名,这样可以实现非常平衡的分布。
† 文件名及其内容被创建为
perl -MPath::Tiny -wE'
path("dir/s".$_.".txt")->spew("A|some_id|$_\n") for 1..500_000
'
【讨论】:
它适用于我的笔记本电脑,但不适用于安装了 Perl 5.10 的 Enterprise Linux。如何手动复制 Path::Tiny 模块并在此处使用? @stack0114106 啊,那个--看着Path::Tiny's code ...似乎您可以将文件保存在您想要的位置?我没有看到任何令人不安的依赖关系,而且都是一个文件:)。或者你当然可以只写一个字符串到一个文件...但是Path::Tiny
确实经常超级方便:)【参考方案3】:
这是我经常执行的任务。其中一些已经在各种 cmets 中提到过。这些对 Perl 来说都不是特别的,最大的胜利将来自改变环境而不是语言。
将文件分段到单独的目录中以保持目录较小。较大的目录需要更长的时间来读取(有时是指数级的)。这发生在任何产生文件的地方。文件路径类似于 .../ab/cd/ef/filename.txt 其中 ab/cd/ef 来自一些不太可能发生冲突的函数。或者可能就像 .../2018/04/01/filename.txt。
您可能对生产者没有太多控制权。我会调查使其将行添加到单个文件中。其他东西稍后会产生单独的文件。
更频繁地运行并将处理过的文件移动到其他地方(同样,可能使用散列。
持续运行并定期轮询目录以检查新文件。
并行运行程序。如果你有很多空闲的核心,让他们去处理它。你需要一些东西来决定谁来做什么。
不要创建文件,而是将它们推送到轻量级数据存储中,例如 Redis。或者可能是重量级数据存储。
实际上并不读取文件内容。请改用 File::Mmap。对于非常大的文件,这通常是一个胜利,但我没有在大量的小文件上使用它。
获得更快的旋转磁盘或 SSD。不幸的是,我不小心在慢速磁盘上的单个目录中创建了数百万个文件。
【讨论】:
【参考方案4】:我认为没有人提出它,但您是否考虑过运行一个长期进程,将文件系统通知用作近实时事件,而不是批量处理?我确信 CPAN 会为 Perl 5 提供一些东西,Perl 6 中有一个内置对象来说明我的意思https://docs.perl6.org/type/IO::Notification 也许其他人可以插话什么是 P5 中使用的好模块?
【讨论】:
Linux::Inotify2(示例见this post)以上是关于Perl 程序高效处理目录中的 500,000 个小文件的主要内容,如果未能解决你的问题,请参考以下文章
500,000 个已排序整数数组上的 C++ 快速排序算法中的 Seg 错误