为什么与 Windows 的 dir 命令相比,运行 opendir、readdir 和 stat 命令如此缓慢?

3

我有一个Perl脚本,使用opendir函数读取目录的内容:

opendir ( DIR, $path ) or next;
    while (my $file = readdir DIR) {

然后我正在执行:

  • -s $file 获取每个文件的大小
  • (stat($file))[9] 获取每个文件的修改时间

我是从Windows机器上运行这个脚本,并访问Ubuntu 14.04上的Samba共享。

这一切都很好,但与在同一文件夹上运行dir列表相比,该过程似乎运行得非常缓慢。

有人知道为什么使用opendir所需的时间比dir列表长得多吗?是否有任何方法可以更改我的脚本以加快速度?


1
只要你调用了 stat,就可以使用 (stat($file))[7] 来获取文件大小。 - mob
1
@mob 你能告诉我使用 stat 和 -s 的区别吗?谢谢。 - A-Kay
@A-Kay 文件测试操作符如 -s 实际上在幕后执行了一个 stat 系统调用,因此如果您在调用 -s 后紧接着调用 stat,那么您就会进行两个系统调用,而只需要一个。 - ThisSuitIsBlackNot
perlportstat 的说明如下: "在 Win32 上,stat() 需要打开文件以确定链接计数并更新可能通过硬链接更改的属性。将 ${^WIN32_SLOPPY_STAT} 设置为真值可以通过不执行此操作来加速 stat()。" 尝试一下这个方法,再加上 mob 的建议,看看是否能加快速度。我想在 Samba 共享上打开文件是耗时的。 - ThisSuitIsBlackNot
@ThisSuitIsBlackNot 谢谢你 - 使用 WIN32_SLOPPY_STAT 真的让我的工作加速了。感谢! - A-Kay
显示剩余2条评论
2个回答

3
根据 perlport
在Win32上,`stat()`需要打开文件以确定链接计数并更新可能通过硬链接更改的属性。将`${^WIN32_SLOPPY_STAT}`设置为真值可以通过不执行此操作来加快`stat()`的速度。
由于您正在访问的文件位于Samba共享上,因此打开它们可能非常耗时。此外,`-s`在幕后进行了一次`stat`系统调用,因此调用`-s`后跟着`stat`是浪费的。
以下代码应该更快:
local ${^WIN32_SLOPPY_STAT} = 1;

opendir my $dh, $path or die "Failed to opendir '$path': $!";

while (my $file = readdir $dh) {
    my ($size, $mtime) = (stat $file)[7, 9];

    say join "\t", $file, $size, $mtime;
}

-s $file 后面跟着 -M _ 怎么样?根据文档-M _ 不会进行第二次 stat(),而是使用上一次 stat 的结果,即只调用 一次 stat - PerlDuck
1
@PerlDog 这只会产生一个 stat 调用,但会得到不同的结果。-M 返回脚本开始时间减去文件修改时间(mtime),以天为单位;而 stat 返回修改时间的 epoch 秒数。 - ThisSuitIsBlackNot
真的,但添加 $^T 不会有害。 - PerlDuck
@PerlDog 当然,你可以添加一堆逻辑来将“-M”的结果从天转换为秒,并从“$^T”中减去它,但是为什么你不愿意使用单个“stat”呢? - ThisSuitIsBlackNot
哈哈,好的,你赢了。 :-) 只是想告诉你,使用“_”作为文件句柄/名称可以避免对“stat”的额外调用。 - PerlDuck

0

我猜测Dir会非常快,因为它是二进制代码并且经过了优化,所以可以快速检索和格式化信息。

在您的脚本中,似乎正在执行多个调用,其中一个是时间,另一个是大小。即使Perl中的较低级别调用是二进制代码,但要获取信息,可能仍需通过几个层次。您可以通过保存stat返回的值并访问所需的部分来减少调用次数,例如:

@items = stat($file);
$size = $items[7];
$modified = $items[9];

这样可以节省一个调用并可能加快脚本的速度。

如果您想要所有文件,可以考虑执行系统调用以执行目录命令并将输出重定向到文件,之后您可以解析文件以获取时间和大小信息。根据文件数量,这可能会更快一些。(/4将是一个4位数的年份,/t:w将是最后一次写入/修改的时间,/c将去掉大小中的逗号)

system("dir /4 /t:w /-c $path > tempList.txt");

然后打开并解析重定向文件以获取所需的信息。
open my $in,"tempList.txt" die "Unable to open file tempList.txt";
my @lines = <$in>;
close($in);
chomp(@lines);

foreach ( @lines ) 
{
  next if ( ! ( m/^\d{4}\/\d{2}\/\d{2}\s+ ); # Not a line with a file
  @parts = split('\s+');
  # Get the parts you need (time and size, where you may have to some other
  # work to get it in the desired format
  #.....
}

如果您想在测试过程中处理行,可以尝试添加正则表达式进行匹配并提取所需的项。这可能会节省一些时间和精力。


谢谢@Glenn。这里的主要问题是dir不会给我秒数,所以我无法将其与我已经从stat获取的当前时代进行比较。我找到了一个解决方法,通过使用forfiles /c“cmd /c echo @file @ftime”来给我秒数,但是这在UNC路径上不起作用(我的路径就是)。使用dir会是我唯一的前进方式吗?我发现使用当前方法比dir慢20倍。 - A-Kay
非常感谢您,@A-Kay。如果您需要使用UNC路径,我建议查找net命令,其中您可以使用net子命令将UNC路径挂载为驱动器。这可能对您想要实现的目标有所帮助。如果您需要dir的速度,只使用分钟进行比较是否足够精细,这也是可能的。 - Glenn
@Glenn:这个方法也可以每行获取一个文件吗? @filelist=\ls -1` ` 我觉得通过循环数组更容易,特别是当我需要使用调试器查看数组内容时。 - Bulrush
1
@Bulrush:如果文件数量不大且命令为@filelist = dir /4 /t:w /-c $path,因为这是Windows机器,那么这将起作用。每行都需要进行额外的解析。它可能会更快,因为您正确,您不必从文件中读取。 - Glenn
1
谢谢你教我而不是批评我。所以opendir()在所有操作系统上都可以工作,并且是首选方法?因为我只在Linux上工作,所以我使用了我所用的格式。 - Bulrush
@Glenn 我宁愿不映射驱动器。实际上,我只是有时调用-s和stat[9] - 我认为特别调用[9]比对文件执行stat并提取所需内容更快 - 是这样吗?通常我一次只做一个,所以可能不会节省很多时间,但在那些重叠的情况下,这将为我节省时间 - 这对我来说非常方便,因为我正在处理数百万个文件。所以谢谢! - A-Kay

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接