Как я могу предварительно выделить строку в Perl? - PullRequest
13 голосов
/ 02 мая 2009

У меня есть Perl-скрипт, который обрабатывает много данных. Существует множество строковых переменных, которые начинаются с малого, но растут очень долго из-за многократного использования оператора точка (конкатенация). Приведет ли рост строки таким образом к повторным перераспределениям? Если да, есть ли способ предварительно выделить строку?

Ответы [ 6 ]

15 голосов
/ 02 мая 2009

Да, расширение строки в Perl приведет к повторным перераспределениям. Perl выделяет немного дополнительного пространства для строк, но только несколько байтов. Вы можете увидеть это, используя Devel :: Peek. Это перераспределение очень быстрое и часто фактически не копирует память. Доверьтесь своему диспетчеру памяти, поэтому вы программируете на Perl, а не на C. Сначала сравните его!

Вы можете предварительно выделить массивы с помощью $#array = $num_entries и хеш с keys %hash = $num_keys, но length $string = $strlen не работает. Вот хитрый трюк , который я выкопал на Perlmonks .

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

Или, если вы хотите попасть в XS, вы можете позвонить SvGROW().

предложение хаоса использовать массив, а затем соединить его вместе, будет использовать более чем вдвое больше памяти. Память для массива. Память для каждого скаляра выделяется для каждого элемента в массиве. Память для строки хранится в каждом скалярном элементе. Память на копию при присоединении. Если это приводит к более простому коду, делайте это, но не думайте, что вы экономите память.

7 голосов
/ 02 мая 2009

Строки 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() ));
7 голосов
/ 02 мая 2009

Альтернативное предложение, с которым будет намного легче справиться: протолкните строки в массив и соедините , когда вы закончите.

3 голосов
/ 05 мая 2009

Я не знаю конкретно, как реализованы строки Perl, но довольно неплохо предположить, что это постоянное амортизированное время . Это означает, что даже если вы найдете способ заранее выделить ваши строковые шансы, то, что объединенное время, которое он сэкономит для всех пользователей скрипта, будет меньше, чем время, которое вы потратили на этот вопрос в переполнении стека .

0 голосов
/ 02 мая 2009

Я бы пошел массив / объединить путь:

push(@array, $crunched_bit)

И затем $str = join('', @array), если не больше, чтобы иметь доступ ко всем элементам для отладки через некоторое время.

0 голосов
/ 02 мая 2009

Да, заранее растянутые строки, которые, как вы знаете, будут расти, - это хорошая идея.

Вы можете использовать оператор 'x', чтобы сделать это. Например, чтобы предварительно выделить 1000 пробелов:

$ s = "" x 1000:

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...