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

How to get a user-configurable buffer for printing?

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

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

我想象的是这样的:

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();

有什么推荐吗?我可能没有使用正确的关键字 print/buffer 我没有在 CPAN 中找到明确的匹配项。

更新: 感谢大家提出非常有用的意见。正如你们中的一些人所指出的,这个问题比我最初想象的要复杂,而且可能是个坏主意。 (这个问题出现是因为我在每次循环迭代时用打印语句打印非常大的文件[> 100Gb],并注意到如果我打印每百次迭代我有一个加速,但这可能取决于循环的方式改变...)

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

我也没有在 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";
}

I'd like to have a print function supporting a user-configurable buffer,   [...]
I imagine something like this:   [...]

写这样的东西并不难。这是一个基本草图

文件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;

这里还有很多事情要做,还有很多可以添加的,因为它只依赖于基本工具,所以可以 add/change 随心所欲。 首先,我可以想象一个 size 方法来操纵现有 object 的缓冲区大小(如果数据已经多于新大小则打印),以及 flush.

请注意,DESTROY 方法规定在 object 退出任何范围并被销毁时打印缓冲区,这似乎是合理的做法。

一个driver

use warnings;
use strict;
use feature 'say';

use PrintBuffer;

my $fout = shift // die "Usage: [=11=] out-file\n";

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)";

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

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

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


print 使用 autoflush 句柄。尽管如此,第一个更改可能是改用 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} 可能非常大,复制它 "to optimize" 取消引用可能会变慢)。

天真地我们只需要 syswrite(FH, SCALAR) 但如果碰巧不是所有 SCALAR 都被写入那么我们需要从过去写入的内容继续写入,因此需要使用表格还有 length-to-write 和偏移量。

因为这是无缓冲的,所以不能与缓冲 IO 混合(或者需要非常小心地完成);查看文档。此外,:encoding 层不能与它一起使用。考虑针对此 class.

中可能需要的其他功能的这些限制