Возвращает весь массив из подпрограммы Perl неэффективно?

У меня часто есть подпрограмма в Perl, которая заполняет массив некоторой информацией. Поскольку я также привык к взлому на С++, я часто нахожу это в Perl, используя ссылки:

my @array;
getInfo(\@array);

sub getInfo {
   my ($arrayRef) = @_;
   push @$arrayRef, "obama";
   # ...
}

вместо более простой версии:

my @array = getInfo();

sub getInfo {
   my @array;
   push @array, "obama";
   # ...
   return @array;
}

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

Это правильно? Или Perl оптимизирует это в любом случае?

Ответ 1

Как насчет возврата ссылки на массив в первую очередь?

sub getInfo {
  my $array_ref = [];
  push @$array_ref, 'foo';
  # ...
  return $array_ref;
}

my $a_ref = getInfo();
# or if you want the array expanded
my @array = @{getInfo()};

Изменить в соответствии с комментарием dehmann:

Также возможно использовать нормальный массив в функции и вернуть ссылку на него.

sub getInfo {
  my @array;
  push @array, 'foo';
  # ...
  return \@array;
}      

Ответ 2

Передача ссылок более эффективна, но разница не такая большая, как в С++. Значения самих аргументов (это означает: значения в массиве) всегда передаются по ссылке (в любом случае возвращенные значения копируются).

Вопрос: это имеет значение? В большинстве случаев это не так. Если вы возвращаете 5 элементов, не беспокойтесь об этом. Если вы возвращаете/пропускаете 100 000 элементов, используйте ссылки. Только оптимизируйте его, если это узкое место.

Ответ 3

Если я посмотрю ваш пример и подумаю о том, что вы хотите сделать, я привык писать его таким образом:

sub getInfo {
  my @array;
  push @array, 'obama';
  # ...
  return \@array;
}

Мне кажется, что это простая версия, когда мне нужно вернуть большой объем данных. Нет необходимости выделять массив вне sub, как вы пишете в своем первом фрагменте кода, потому что my сделает это за вас. В любом случае вам не следует делать преждевременную оптимизацию Leon Timmermans предлагать.

Ответ 4

Чтобы ответить на окончательное размышление, нет, Perl не оптимизирует это. Это не так, потому что возврат массива и возврат скаляра принципиально отличаются.

Если вы имеете дело с большими объемами данных или если производительность представляет собой серьезную проблему, то ваши привычки C будут служить вам хорошо и направлять ссылки на структуры данных, а не сами структуры, чтобы они не нуждались в скопировать. Но, как отметил Леон Тиммерманс, подавляющее большинство времени, вы имеете дело с меньшими объемами данных и производительности, не так уж и важны, поэтому сделать это каким-либо образом представляется наиболее читаемым.

Ответ 5

Так я обычно возвращаю массив.

sub getInfo {
  my @array;
  push @array, 'foo';
  # ...
  return @array if wantarray;
  return \@array;
}

Таким образом, он будет работать так, как вы хотите, в скалярном или контекстном списке.

my $array = getInfo;
my @array = getInfo;

$array->[0] == $array[0];

# same length
@$array == @array;

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

Ответ 6

Есть два соображения. Очевидным является то, насколько большой будет ваш массив? Если это меньше нескольких десятков элементов, тогда размер не является фактором (если вы не оптимизируете микро-функцию для некоторой быстро вызываемой функции, но вам придется сделать некоторое профилирование памяти, чтобы доказать это в первую очередь).

Это легкая часть. Часто игнорируется второе соображение - интерфейс. Как будет использоваться возвращаемый массив? Это важно, потому что разыменование целых массивов в Perl выглядит ужасно. Например:

for my $info (@{ getInfo($some, $args) }) {
    ...
}

Это уродливо. Это намного лучше.

for my $info ( getInfo($some, $args) ) {
    ...
}

Он также поддается отображению и grepping.

my @info = grep { ... } getInfo($some, $args);

Но возврат массива ref может быть удобным, если вы собираетесь выбирать отдельные элементы:

my $address = getInfo($some, $args)->[2];

Это проще, чем:

my $address = (getInfo($some, $args))[2];

Или:

my @info = getInfo($some, $args);
my $address = $info[2];

Но в этот момент вы должны задать вопрос, является ли @info действительно списком или хешем.

my $address = getInfo($some, $args)->{address};

То, что вам не следует делать, это getInfo() вернуть массив ref в скалярном контексте и массив в контексте списка. Это путает традиционное использование скалярного контекста как длины массива, что удивит пользователя.

Наконец, я подключу свой собственный модуль, Method::Signatures, потому что он предлагает компромисс для передачи ссылок на массивы без необходимости использования синтаксис ref массива.

use Method::Signatures;

method foo(\@args) {
    print "@args";      # @args is not a copy
    push @args, 42;   # this alters the caller array
}

my @nums = (1,2,3);
Class->foo(\@nums);   # prints 1 2 3
print "@nums";        # prints 1 2 3 42

Это делается через магию Data::Alias.

Ответ 7

3 другие потенциально большие улучшения производительности, если вы читаете весь, довольно большой файл и нарезаете его в массив:

  • Отключить BUFFERING с помощью sysread() вместо read() (вручную предупреждает о смешивании)
  • Предварительно расширьте массив, оценив последний элемент -   экономит выделение памяти
  • Используйте Unpack() для быстрого разделения данных, таких как данные графического канала uint16_t.

Передача массива ref в функцию позволяет основной программе иметь дело с простым массивом, в то время как функция write-once-and-forget worker использует более сложные формы доступа "$ @" и arrow → [$ II]. Будучи вполне C'ish, он, скорее всего, будет быстрым!

Ответ 8

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

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

Эффективное решение заключается в том, что подпрограмма передает вызывающей программе адрес массива. Как я уже сказал, я не имею в виду поведение по умолчанию в Perl. Но некоторые языки предоставляют программисту возможность выбрать, какой подход.