代码之家  ›  专栏  ›  技术社区  ›  sigjuice

如何在Perl中预先分配字符串?

  •  13
  • sigjuice  · 技术社区  · 17 年前

    我有一个处理大量数据的Perl脚本。有许多字符串变量开始时很小,但由于重复使用点(concatetation)运算符而增长得很长。以这种方式增长字符串会导致重复的重新分配吗?如果是,是否可以预先分配字符串?

    6 回复  |  直到 10 年前
        1
  •  7
  •   Sinan Ünür    11 年前

    其他更容易处理的建议: push 数组中的字符串 join 完成后。

        2
  •  15
  •   Schwern    17 年前

    是的,Perl增大字符串将导致重复的重新分配。Perl为字符串分配了一点额外的空间,但只分配了几个字节。您可以使用devel::peek看到这个。这种重新分配非常快,而且通常不会复制内存。相信您的内存管理器,这就是为什么您要用Perl而不是C进行编程。请首先对它进行基准测试!

    可以使用 $#array = $num_entries 和哈希一起 keys %hash = $num_keys 但是 length $string = $strlen 不起作用。这里有一个 clever trick I dug up on Perlmonks .

    my $str = "";
    vec($str, $length, 8)=0;
    $str = "";
    

    或者如果你想进入XS,你可以打电话给 SvGROW() .

    混沌建议使用一个数组,然后将其全部连接在一起,这将使用双倍以上的内存。阵列的内存。为数组中的每个元素分配的每个标量的内存。存储在每个标量元素中的字符串的内存。连接时的副本内存。如果它导致代码更简单,那么就这样做,但不要认为您在保存任何内存。

        3
  •  7
  •   Peter Mortensen Pieter Jan Bonestroo    16 年前

    Perl的字符串是可变的,因此附加到字符串可以 不是 招致字符串复制惩罚。

    你可以尝试所有你想找到的“更快”的方法,但这闻起来很糟糕的过早优化。

    举个例子,我创建了一个抽象化了艰苦工作的类。它工作得很好,但它的所有愚蠢的伎俩,真的很慢。

    结果如下:

             Rate  magic normal
    magic  1.72/s     --   -93%
    normal 23.9/s  1289%     --
    

    是的,没错,Perl比我认为的值得尊敬的实现快1200%。

    分析你的代码并找出真正的问题是什么,不要尝试优化那些甚至不是已知问题的东西。

    #!/usr/bin/perl
    
    use strict;
    use warnings;
    
    {
    
        package MagicString;
        use Moose;
    
        has _buffer => (
            isa => 'Str',
            is  => 'rw',
        );
        has _buffer_size => (
            isa     => 'Int',
            is      => 'rw',
            default => 0,
        );
        has step_size => (
            isa     => 'Int',
            is      => 'rw',
            default => 32768,
        );
        has _tail_pos => (
            isa     => 'Int',
            is      => 'rw',
            default => 0,
        );
    
        sub BUILD {
            my $self = shift;
            $self->_buffer( chr(0) x $self->step_size );
        }
    
        sub value {
            my $self = shift;
            return substr( $self->{buffer}, 0, $self->{_tail_pos} );
        }
    
        sub append {
            my $self  = shift;
            my $value = shift;
            my $L     = length($value);
            if ( ( $self->{_tail_pos} + $L ) > $self->{_buffer_size } ){
                $self->{buffer} .= (chr(0) x $self->{step_size} );
                $self->{_buffer_size} += $self->{step_size};
            }
            substr( $self->{buffer}, $self->{_tail_pos}, $L, $value );
            $self->{_tail_pos} += $L;
        }
        __PACKAGE__->meta->make_immutable;
    }
    
    
    use Benchmark qw( :all :hireswallclock );
    
    cmpthese( -10 , {
            magic => sub{
                my $x = MagicString->new();
                for ( 1 .. 200001 ){
                    $x->append( "hello");
                }
                my $y = $x->value();
            },
            normal =>sub{
                my $x = '';
                for ( 1 .. 200001 ){
                    $x .= 'hello';
                }
                my $y = $x;
            }
        });
    #use Data::Dumper;
    #print Dumper( length( $x->value() ));
    
        4
  •  3
  •   Community Mohan Dere    9 年前

    我不知道Perl字符串是如何实现的,但一个很好的猜测是 constant amortized time . 这意味着,即使您确实找到了一种预先分配字符串的方法,它为所有脚本用户节省的总时间也将少于您花在请求上的时间。 this question 堆栈溢出时。

        5
  •  0
  •   Peter Mortensen Pieter Jan Bonestroo    16 年前

    我会去阵列/连接方式:

    push(@array, $crunched_bit)
    

    然后 $str = join('', @array) 如果没有更多的内容,可以访问所有元素以便稍后进行调试。

        6
  •  -2
  •   Kevin Beck    17 年前

    是的,预先扩展你知道会增长的字符串是个好主意。

    您可以使用“x”运算符来执行此操作。例如,要预分配1000个空间:

    $S=“”x 1000: