如何获得用户可配置的打印缓冲区



我想要一个支持用户可配置缓冲区的打印功能,所以只有当缓冲区大于阈值时才能打印缓冲区中的内容(。

我需要写多个文件,所以我有多个文件句柄可以写,因此面向对象的模块可能更方便。

我想是这样的:

my $printer1 = Print::Buffer->new({ size => 1000, filehandle => $OUT1 });
for (my $i=1; $i<1000; $i++) {
$printer1->print("This string will be eventually printed ($i/1000)");
}
# and at the end print the remaining buffer
$printer1->flush();

有什么建议吗?我可能没有使用正确的关键字,因为对于打印/缓冲区,我在CPAN中没有找到明确的匹配项。

更新:感谢大家的宝贵意见。正如你们中的一些人所指出的,这个问题比我最初想象的要复杂,可能是个坏主意。(这个问题是在我在每次循环迭代中打印非常大的文件[>100Gb]时产生的,并注意到如果我在每一百次迭代中打印,我会有一个加速,但这可能取决于循环是如何改变的…(

更新2:我需要/想要接受一个答案。对我来说,两者都很有启发性,都很有用。我测试了两者,在能够对改进进行基准测试之前,它们都需要进一步的工作(如果有的话,请参阅上面的更新(。tie手柄是我喜欢的一个鲜为人知的功能,这就是我接受它的原因。在我看来,他们两个都同样接近预期的答案。非常感谢大家的讨论和见解。

我想要一个支持用户可配置缓冲区的打印功能;[…]
我想象这样的事情:;[…]

写这样的东西并不难

文件PrintBuffer.pm

package PrintBuffer;
use warnings;
use strict;
sub new { 
my ($class, %args) = @_; 
my $self = { 
_size => $args{size}       // 64*1024,            #//
_fh   => $args{filehandle} // *STDOUT,
_buf  => ''
};  
$self->{_fh}->autoflush;  # want it out once it's printed
bless $self, $class;
}
sub print {
my ($self, $string) = @_; 
$self->{_buf} .= $string;
if ( length($self->{_buf}) > $self->{_size} ) { 
print { $self->{_fh} } $self->{_buf};
$self->{_buf} = ''; 
}
return $self;
}
sub DESTROY {
my $self = shift;
print { $self->{_fh} } $self->{_buf}  if $self->{_buf} ne ''; 
$self->{_buf} = ''; 
}
1;

这里还有很多事情要做,还有很多事情可以添加,因为它只依赖于基本工具,所以可以根据需要添加/更改首先,我可以想象一个size方法来处理现有对象的缓冲区大小(如果已经有比新大小更多的数据,则打印(和flush

注意,DESTROY方法提供了在对象退出任何范围并被销毁时打印的缓冲区,这似乎是合理的做法

驱动

use warnings;
use strict;
use feature 'say';
use PrintBuffer;
my $fout = shift // die "Usage: $0 out-filen";
open my $fh, '>', $fout  or die "Can't open $fout: $!";
my $obj_file   = PrintBuffer->new(size => 100, filehandle => $fh);
my $obj_stdout = PrintBuffer->new(size => 100);
$obj_file->print('a little bit');
$obj_stdout->print('a little bit');
say "printed 'a little bit' ..."; sleep 10;
$obj_file->print('out'x30);                 # push it over a 100 chars
$obj_stdout->print('out'x30);
say "printed 'out'x30 ... "; sleep 10;
$obj_file->print('again...');               # check  DESTROY
$obj_stdout->print('again');
say "printed 'again' (and we're done)";

每次打印信息后,请检查另一个终端中输出文件的大小。

我尝试了Grinz在评论中提出的PerlIO::buffersize,它似乎像他们说的那样"像广告宣传的那样"起作用。它不允许你随心所欲,但它可能是满足基本需求的现成解决方案。请注意,这不适用于正在使用的:encoding层。

感谢ikegami的评论和测试(链接在评论中(。


printautoflush的句柄一起工作。尽管如此,第一个变化可能是使用syswrite,它是无缓冲的,并试图通过一个write(2)调用直接写入所有要求它的内容。但由于无法保证所有内容都已写入,我们还需要检查

use Carp;  # for croak
WRITE: {
my $bytes_written = 0;
while ( $bytes_written < length $self->{_buf} ) {
my $rv = syswrite( 
$self->{_fh}, 
$self->{_buf}, 
length($self->{_buf}) - $bytes_written,
$bytes_written
);
croak "Error writing: $!" if not defined $rv;
$bytes_written += $rv;
}
$self->{_buf} = '';
};

我把它放在一个块中只是为了限制$bytes_written和任何其他可能希望引入的变量的范围,以减少$self的解引用数量(但请注意,$self->{_buf}可能相当大,复制它以"优化"解引用可能会更慢(。

天真地,我们只需要syswrite(FH, SCALAR),但如果碰巧不是所有的SCALAR都被写入,那么我们需要继续从过去写入的内容,因此需要使用带长度的形式来写入和偏移。

由于这是无缓冲的,所以不能与缓冲的IO混合(或者需要非常小心地进行(;请参阅文档。此外,:encoding层不能与它一起使用。请考虑针对此类中可能需要的其他功能的这些限制。

我也没有看到CPAN的通用解决方案。但对于绑定的文件句柄来说,这已经足够简单了。类似的东西

use Symbol;
sub Print::Buffer::new {
my ($class,$mode,$file,@opts) = @_;
my $x = Symbol::gensym;
open ($x, $mode, $file) or die "failed to open '$file': $!";
tie *$x, "Print::Buffer", fh => $fh, @opts;
$x;
}
sub Print::Buffer::TIEHANDLE {
my $pkg = shift;
my $self = { @_ };
$self->{bufsize} //= 16 * 1024 * 1024;
$self->{_buffer} = "";
bless $self, $pkg;
}
sub Print::Buffer::PRINT {
my ($self,@msg) = @_;
$self->{buffer} .= join($,,@msg);
$self->_FLUSH if length($self->{buffer}) > $self->{bufsize};
}
sub Print::Buffer::_FLUSH {
my $self = shift;
print  {$self->{fh}}  $self->{buffer};
$self->{buffer} = "";
}
sub Print::Buffer::CLOSE {
my $self = shift;
$self->_FLUSH;
close( $self->{fh} );
}
sub Print::Buffer::DESTROY {
my $self = shift;
$self->_FLUSH;
}
#  ----------------------------------------
my $fh1 = Print::Buffer->new(">", "/tmp/file1", 
bufsize => 16*1024*1024);
for (my $i=1; $i<1000; $i++) {
print $fh1 "This string will be eventually printed ($i/1000)n";
}

最新更新