Производительность со строками Perl

Я сталкивался с большим количеством кода Perl, который разбивает длинные строки следующим образом:

my $string = "Hi, I am a very long and chatty string that just won't";
$string .= " quit.  I'm going to keep going, and going, and going,";
$string .= " kind of like the Energizer bunny.  What are you going to";
$string .= " do about it?";

Судя по моему опыту работы с Java, построение такой строки было бы неэффективным. Верно ли то же самое с Perl? В своих поисках я прочитал, что использование join в массиве строк — это самый быстрый способ конкатенации строк, но что делать, если вы просто хотите разбить строку для удобства чтения? Лучше написать:

my $string = "Hi, I am a very long and chatty string that just won't" .
    " quit.  I'm going to keep going, and going, and going," .
    " kind of like the Energizer bunny.  What are you going to" .
    " do about it?";

Или мне использовать join, или как это сделать?


person justkt    schedule 23.06.2010    source источник
comment
Некоторые считают первую форму наиболее читаемой. И я не вижу реальных оснований для беспокойства о том, что быстрее.   -  person ysth    schedule 24.06.2010
comment
@ysth - вы когда-нибудь были программистом на Java? Тогда бы вы :).   -  person justkt    schedule 24.06.2010
comment
Это не имеет смысла. Потому что Java такая медленная? Потому что Java-программисты — упрямые преждевременные оптимизаторы?   -  person ysth    schedule 24.06.2010
comment
@ysth - потому что строки Java неизменяемы. Таким образом, выполнение myString += " more string information"; требует определенных накладных расходов, которых может не быть для строки, которую можно редактировать на месте. Если кто-то со временем создает строку в Java, обычно используется StringBuilder или аналогичный.   -  person justkt    schedule 24.06.2010
comment
Неизменяемость не меняет основного факта, что строки, достаточно короткие, чтобы они были встроены в ваш код, крайне маловероятно когда-либо будут играть какую-либо роль в производительности, как бы вы их ни собирали. Об этом просто не стоит беспокоиться на любом языке; идите с тем, что наиболее читабельно для вас.   -  person ysth    schedule 25.06.2010
comment
Это очень опасное отношение. Хотя, да, конечно, преждевременная оптимизация больше вредит, чем приносит пользу, говоря кому-то, кто на самом деле пытается найти такие вещи, что им не о чем беспокоиться, превращает плохих программистов Java в худших программистов Perl. Потому что Perl дает вам не только достаточно веревки, чтобы повеситься, но и полную свободу выбора дерева.   -  person Cornelius    schedule 12.10.2011
comment
@justkt: в Java string1 + string2 + string3 точно эквивалентно new StringBuilder( string1 ).append( string2 ).append( string3 ).toString(). Компилятор Java использует StringBuilder внутри для реализации конкатенации. Большинство версий компилятора также достаточно интеллектуальны, чтобы оптимизировать серию конкатенированных строковых литералов в один литерал.   -  person Sam Hanes    schedule 25.01.2012


Ответы (7)


Книга о верблюдах, стр. 598:

Предпочитайте join("", .. .) серии объединенных строк. Множественные конкатенации могут привести к многократному копированию строк туда и обратно. Оператор соединения позволяет избежать этого.

person Justin R.    schedule 23.06.2010
comment
Это могло быть обрядом в то время, когда это было написано. Его нет в Perl 5.18. perl -Mstrict -MBenchmark -we 'timethis -10, sub {my $output= ""; foreach (1 .. 10000) {$output .= chr(rand(127))x1234; } }' на моей машине возвращает 11 wallclock secs (10.71 usr + 0.00 sys = 10.71 CPU) @ 491.13/s (n=5260), а perl -Mstrict -MBenchmark -we 'timethis -10, sub { my @chunks; foreach (1 .. 10000) {push @chunks, chr(rand(127))x1234; } my $output = join("",@chunks); }' возвращает 11 секунд настенных часов (10,56 usr + 0,00 sys = 10,56 ЦП) при 134,09/с (n = 1416). Это фактор 3, который .= бьет push + join. - person Georg Mavridis; 08.03.2017

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

print "this is",
    " perfectly legal",
    " because print will happily",
    " take a list and send all the",
    " strings to the output stream\n";

die "this is also",
    " perfectly acceptable";

use Log::Log4perl :easy; use Data::Dumper;
INFO("and this is just fine",
    " as well");

INFO(sub {
    local $Data::Dumper::Maxdepth = 1;
    "also note that many libraries will",
    " accept subrefs, in which you",
    " can perform operations which",
    " return a list of strings...",
    Dumper($obj);
 });
person Ether    schedule 23.06.2010

Я сделал эталон! :)

#!/usr/bin/perl

use warnings;
use strict;

use Benchmark qw(cmpthese timethese);

my $bench = timethese($ARGV[1], {

  multi_concat => sub {
    my $string = "Hi, I am a very long and chatty string that just won't";
    $string .= " quit.  I'm going to keep going, and going, and going,";
    $string .= " kind of like the Energizer bunny.  What are you going to";
    $string .= " do about it?";
  },

  one_concat => sub {
    my $string = "Hi, I am a very long and chatty string that just won't" .
    " quit.  I'm going to keep going, and going, and going," .
    " kind of like the Energizer bunny.  What are you going to" .
    " do about it?";
  },

  join => sub {
    my $string = join("", "Hi, I am a very long and chatty string that just won't",
    " quit.  I'm going to keep going, and going, and going,",
    " kind of like the Energizer bunny.  What are you going to",
    " do about it?"
    );
  },

} );

cmpthese $bench;

1;

Результаты (на моем iMac с Perl 5.8.9):

imac:Benchmarks seb$ ./strings.pl 1000
Benchmark: running join, multi_concat, one_concat for at least 3 CPU seconds...
      join:  2 wallclock secs ( 3.13 usr +  0.01 sys =  3.14 CPU) @ 3235869.43/s (n=10160630)
multi_concat:  3 wallclock secs ( 3.20 usr + -0.01 sys =  3.19 CPU) @ 3094491.85/s (n=9871429)
one_concat:  2 wallclock secs ( 3.43 usr +  0.01 sys =  3.44 CPU) @ 12602343.60/s (n=43352062)
                   Rate multi_concat         join   one_concat
multi_concat  3094492/s           --          -4%         -75%
join          3235869/s           5%           --         -74%
one_concat   12602344/s         307%         289%           --
person sebthebert    schedule 23.06.2010
comment
one_concat оптимизируется компилятором в постоянное присваивание с 0 конкатенациями во время выполнения. - person Eric Strom; 24.06.2010
comment
@Эрик. Спасибо - это в значительной степени отвечает на мой первоначальный вопрос. - person justkt; 24.06.2010

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

Поэтому, если любой из этих примеров будет в цикле или функции, вызываемой много раз, второй пример будет быстрее.

Это предполагает, что строки известны во время компиляции. Если вы создаете строки во время выполнения, как упоминает fatcat1111, оператор join будет быстрее, чем повторная конкатенация.

person Eric Strom    schedule 23.06.2010

В моих тестах join лишь немного быстрее, чем конкатенация с переназначением, и только для коротких списков строк. Конкатенация без переназначения выполняется значительно быстрее, чем любая из них. В более длинных списках join работает заметно хуже, чем конкатенация с переназначением, вероятно, потому, что передача аргументов начинает доминировать во времени выполнения.

4 strings:
          Rate   .= join    .
.=   2538071/s   --  -4% -18%
join 2645503/s   4%   -- -15%
.    3105590/s  22%  17%   --
1_000 strings:
         Rate join   .=
join 152439/s   -- -40%
.=   253807/s  66%   --

Итак, с точки зрения вашего вопроса, . превосходит .= по времени выполнения, хотя и не настолько, чтобы об этом вообще стоило беспокоиться. Удобочитаемость почти всегда важнее производительности, и .= часто является более читаемой формой.

Это в общем случае; как показывает sebthebert answer, . намного быстрее, чем .= в конкатенации- констант, что у меня возникнет соблазн относиться к этому как к правилу.

(Кстати, бенчмарки в основном в очевидном виде, и я предпочитаю не повторять код здесь. Единственное, что удивительно, это создание начальных строк из <DATA>, чтобы не допустить свертки констант.)

D'A

person darch    schedule 24.06.2010

Используйте тот, который вам больше нравится; производительность их точно такая же в Perl. Строки Perl не похожи на строки Java и могут быть изменены на месте.

person JSBձոգչ    schedule 23.06.2010
comment
Производительность двух примеров одинакова, или производительность join такая же, как и в любом примере? Мне немного трудно поверить, что join - это то же самое (поскольку join обычно является полностью нативным вызовом функции), но если бы какой-либо язык оптимизировал оператор конкатенации строк, я уверен, что это был бы perl.. . - person Weston C; 23.06.2010
comment
Concat не так эффективен, как соединение. Смотрите мой ответ, почему. - person Justin R.; 23.06.2010
comment
Производительность обоих примеров одинакова. Производительность join это что-то другое, которое может быть больше, а может быть меньше. В любом случае perl не является высокопроизводительным языком, и стоимость конкатенации строк или вызова join вряд ли имеет хоть какое-то значение. - person JSBձոգչ; 23.06.2010
comment
Хотя я согласен с тем, что perl не является высокопроизводительным языком, ОП спрашивал не об этом. Он спросил, какое решение работает лучше. Разница может быть в лучшем случае второго порядка, но я бы предпочел дать прямой ответ на прямой вопрос, чем сомневаться в плакате. - person Justin R.; 23.06.2010
comment
Трудно отказаться от многолетних обзоров кода, которые бьются в моей голове. Используйте StringBuilder и тому подобное, чтобы читать код, который в Java выглядит неправильно. Но, надеюсь, это поможет мне писать больше на Perl-подобном Perl! - person justkt; 23.06.2010

Вам не нужно делать ничего из этого, вы можете просто сразу присвоить всю строку переменной.

my $string = "Hi, I am a very long and  chatty string that just won't
 quit.   I'm going to keep going, and going,  and going,
 kind of like the Energizer  bunny.  What are you going to
 do  about it?"; 
person dirk    schedule 23.06.2010
comment
Это будет включать новые строки в строку, что, вероятно, не то, что хочет пользователь. - person Ether; 24.06.2010
comment
@Эфир - правильно. Реквизит для того, чтобы не называть меня он, кстати. Это происходит постоянно, по какой-то причине, когда я не мужчина. - person justkt; 24.06.2010
comment
@justkt: я тоже нет. высокий5 :) - person Ether; 24.06.2010