【问题标题】:How can I pre-allocate a string in Perl?如何在 Perl 中预先分配字符串?
【发布时间】:2010-10-23 06:15:43
【问题描述】:

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

【问题讨论】:

    标签: perl string


    【解决方案1】:

    是的,Perl 增长字符串会导致重复的重新分配。 Perl 为字符串分配了一点额外空间,但只有几个字节。您可以使用 Devel::Peek 看到这一点。这种重新分配非常快,并且通常不会实际复制内存。相信您的内存管理器,这就是您使用 Perl 而不是 C 编程的原因。首先对其进行基准测试!

    您可以使用$#array = $num_entrieskeys %hash = $num_keys 预分配数组,但length $string = $strlen 不起作用。这是clever trick I dug up on Perlmonks

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

    或者如果你想进入 XS,你可以拨打SvGROW()

    chaos 建议使用一个数组,然后将它们全部连接在一起将使用两倍以上的内存。数组的内存。为数组中的每个元素分配的每个标量的内存。每个标量元素中保存的字符串的内存。加入时副本的内存。如果它导致代码更简单,那就去做吧,但不要认为你在节省任何内存。

    【讨论】:

      【解决方案2】:

      更容易处理的替代建议:push 将字符串放到数组中,然后在完成后将 join 它。

      【讨论】:

      • 尽管数组中的每个元素都会创建一个包含所有开销的 SV。这样会消耗更多的内存。
      【解决方案3】:

      Perl 的字符串是可变的,所以附加到一个字符串 NOT 会导致字符串重复惩罚。

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

      举个例子,我创建了一个抽象出繁重工作的课程。它工作得很好,但它的所有愚蠢的技巧,真的很慢。

      结果如下:

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

      【讨论】:

      • 说 Perl 不复制字符串只是事实的一半。 Perl 只为字符串分配了几个额外的字符,因此 Perl 很可能会在追加时增加包含字符串的内存。这可能会导致内存被复制。但这发生在系统的内存管理器中,速度非常快。请记住,在数学课上 O(n) 会击败 O(logn),但在现实世界中,算法的恒定时间很重要。 C 很快。
      • 确实,如果 O(1) 一步需要几天时间,O(1) 就不是很好,而 O(n^2) 可能只需要几秒钟 :) 不过,如果你的数据量太大以至于 O(n^2) 方法超过几周,并且这种大小的数据集很常见。
      【解决方案4】:

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

      【讨论】:

        【解决方案5】:

        我会采用数组/加入方式:

        push(@array, $crunched_bit)
        

        然后$str = join('', @array),如果仅此而已,可以访问所有元素以便稍后进行调试。

        【讨论】:

        • 这将占用相当多的额外内存,因为每个数组元素都需要一个新的 SV。
        【解决方案6】:

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

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

        $s = " " x 1000:

        【讨论】:

        • 然后在赋值的 lhs 上使用 substr。呜呜呜。
        • 虽然这将创建一个包含 1000 个空格的字符串,但当我说“$s = 'foo'”时,我会得到一个仅使用前三个的 1000 个字符的字符串还是会给我一个新的 3 个字符的字符串并把你的扔掉? (我怀疑是后者,但实际上并不知道 perl 将如何处理它。)
        • 如果你重新分配它,它会丢弃旧的结果(假设没有对它的引用)。您需要进行字符串替换,就像戴夫说的那样,只修改其中的一部分。 ++array-then-join
        • Perl 不会丢弃已分配给标量的内存。因此$s = " " x 1000; $s = ""; 将预分配 1000+ 个字节给 $s 并为您留下一个空字符串。然而,Perl 必须计算 " " x 1000 这有点浪费。 vec() 解决方案更好。或者干脆别担心。
        • 对,但是上下文已经消失了——所以用零或空白填充它是浪费时间。
        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2013-11-08
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多