Ответ 1
Альтернативное предложение, с которым будет намного легче справиться: push строки на массив и join, когда вы закончите.
У меня есть Perl script, который сжимает много данных. Существует цепочка строковых переменных, которые начинаются с малого, но растут очень долго из-за повторного использования оператора точки (concatentation). Будет ли рост строки таким образом приводить к повторным перераспределениям? Если да, есть ли способ предварительно выделить строку?
Альтернативное предложение, с которым будет намного легче справиться: push строки на массив и join, когда вы закончите.
Да, 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()
.
Предложение хаоса использовать массив, а затем объединить его вместе, будет использовать более чем вдвое больше памяти. Память для массива. Память для каждого скаляра, выделенного для каждого элемента массива. Память для строки, содержащейся в каждом скалярном элементе. Память для копии при присоединении. Если это приведет к более простому коду, сделайте это, но не думайте, что вы сохраняете какую-либо память.
Строки 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() ));
Я точно не знаю, как реализованы строки Perl, но довольно неплохо предположить, что это постоянное время амортизации. Это означает, что даже если вы найдете способ предопределить свои шансы на строку, это то, что комбинированное время, которое он сохранит для всех пользователей script, будет меньше времени, которое вы потратили на запрос этот вопрос о переполнении стека.
Я бы пошел по массиву /join:
push(@array, $crunched_bit)
И затем $str = join('', @array)
, если не более того, иметь доступ ко всем элементам для отладки в более позднее время.
Да, предпосылка продолжения, которую вы знаете, будет хорошей идеей.
Для этого вы можете использовать оператор "x". Например, чтобы предварительно выделить 1000 пробелов:
$s = "" x 1000: