【问题标题】:does Perl's Capture::Tiny::capture() avoid disk io required when using system()?Perl 的 Capture::Tiny::capture() 是否避免了使用 system() 时需要的磁盘 io?
【发布时间】:2020-02-15 04:00:22
【问题描述】:

当从 Perl 脚本调用外部程序时,Capture::Tiny 是否避免了使用 system() 时需要的磁盘 io?使用任何一种时,我都能获得基本相同的性能。一位同事正在使用我的代码并告诉我它正在敲打他的磁盘。在本地机器上运行并写入本地磁盘时,我(也许)没有这个问题。

我以前是这样做的:

open($fhStdin, ">stdin.txt");
print $fhStdin "some text\n";
close($fhStdin);
system("cmd < stdin.txt 1> stdout.txt 2> stderr.txt"); 
# open and read stdout.txt
# open and read stderr.txt

然后改成这样:

($stdout, $stderr, $exit) = capture {
    open($fhStdin, '| cmd');
    print $fhStdin "some text\n";
    close($fhStdin);
};

但 NYTProf 告诉我,它们运行所需的时间基本相同(但 NYTProf 从子例程时间中消除了磁盘 io 开销)。所以我想知道 capture() 是否正在写入引擎盖下的临时文件? (我尝试阅读 Tiny.pm 源代码,但很惭愧地说我无法从中看出。)

感谢任何提示。

【问题讨论】:

  • 一般来说,您的 Capture::Tiny 实现会减少 IO。它不是读写磁盘。很奇怪。
  • 轻微错字...应该是'system("cmd stdout.txt 2> stderr.txt")',对不起。
  • 您可以使用问题下方的edit 链接edit 您的问题,或使用此edit 链接。 ;)
  • 这两个代码 sn-ps 的行为不同。在第一种情况下,您让 shell 从您第一次编写的文件中管理对cmd 的输入,在第二种情况下,您正在向它提供输入……如何?你能展示一下# write cmd input to $fh 的进展情况吗(简而言之)?就目前而言,我不明白为什么它会导致 more 磁盘访问。
  • @zdim 在第一种情况下,我将外部程序期望的文本打印到文件中,然后让外壳程序将其传递给 cmd 的标准输入(如您所说)。在第二种情况下,我打开一个到 cmd 的管道,从而将相同的文本直接打印到 cmd 的标准输入。 (即避免我的脚本需要编写中间文件。)抱歉,如果不清楚,我已经编辑了我的帖子以(希望)改进这一点。

标签: perl io


【解决方案1】:

Capture::Tiny::capture 的文档指出确实使用了文件

通常对匿名临时文件句柄进行捕获。

这可以在_capture_tee sub 的源代码中看到,用作所有方法的通用例程。大约在这个 sub 进行到一半时,我们发现 call to File::Temp-&gt;new 正在发生,除非要使用命名文件(见下文)。可以小心地跟踪其余的处理过程。

文档继续提供一种通过命名文件监控所有这些的方法

要通过命名文件进行捕获(例如,外部监控长时间运行的捕获),请提供自定义文件句柄作为选项对的尾随列表:

my $out_fh = IO::File->new("out.txt", "w+");
my $err_fh = IO::File->new("out.txt", "w+");
capture { ... } stdout => $out_fh, stderr => $err_fh;  

文件句柄必须是读/写和可查找的。在捕获操作期间修改文件或文件句柄将产生不可预知的结果。它们上的现有 IO 层可能会被捕获更改。

(如果这样做了,那么对File::Temp 的调用就不会进行,如上所述。参见源代码。)

如果此磁盘活动有问题,您可以使用管道open 读取cmd 的输出 (首先将其输入写入文件),或使用qx(反引号)。但是你必须合并或重定向STDERR 并通过更多的箍来检查和处理错误。

另一种选择是使用IPC::Run3。虽然它也 uses files 它提供了更多的选项,可以用来减少磁盘 I/O,或者完全避免磁盘。 (使用打开到标量的文件句柄调用(内存中)的想法不起作用,因为这不是真正的文件句柄。

“核”选项是更复杂的IPC::Run,它可以在不使用磁盘的情况下获取输出。


粗略的草图

将所有方法“分派”到_capture_tee 完成in the beginning,其中一组标志是unshifted 到@_,然后goto &amp;func 将其带走,以区分方法。对于capture,这是1,1,0,0,在_capture_tee 中设置变量$do_stdout$do_stderr。然后这些用于设置%do hash,其中的键是iterated over to set up $stash

如果额外的参数被传递给capture(对于命名文件),那么$stash-&gt;{capture} is set,否则分配一个File::Temp对象。 $stash 是稍后发生重定向的passed to _open_std

还有很多,但主要与本地化全局和图层的操作有关。


最常见的调用写入标量

run3 \@cmd, \my $in, \my $out, \my $err;

但这使用文件,如How it works 下的文档中所述。

试图通过写入打开到标量的文件句柄来欺骗它不使用文件

my @cmd = qw(ls -l .);
open my $fh, '>', \my $cmd_out;  # not a real filehandle ...
run3 \@cmd, \undef, $fh;         # ... so this won't work

中止

run3():在 ... 处重定向 STDOUT 的参数无效

这是因为标量的open 没有设置真正的文件句柄。见this post

如果文件句柄打开到一个文件,这将按预期工作,写入该文件。与Capture::Tiny 相比,这很可能会导致更高效的磁盘 I/O 操作。

【讨论】:

  • 输出到标量的run3 命令不应该使用对标量的引用:run3 \@cmd, \undef, \my $cmd_out,还是我遗漏了什么?
  • @HåkonHægland 对,但该调用使用文件(它在链接中解释)。我认为它可能会被欺骗 - 写入文件句柄(“如果重定向器已经指定了一个文件句柄,它只是使用它”)但将其打开为标量......但它没有似乎有效。
  • @HåkonHægland 编辑了该脚注,希望我的尝试现在更清晰。谢谢
  • 非常感谢您的详细回复,并提供可能的替代方法。我见过 File::Temp 的使用(我也在使用 system() 的版本中使用它),但我不知道它是用于所有流还是其他东西。
  • @zdim 是的,我想我现在明白这个问题了。感谢您的澄清!
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2015-09-14
  • 2021-05-07
  • 2014-12-21
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多