Является ли возврат целого массива из подпрограммы Perl неэффективным? - PullRequest
14 голосов
/ 13 февраля 2009

У меня часто есть подпрограмма в Perl, которая заполняет массив некоторой информацией. Поскольку я также привык к взлому в C ++, я часто делаю это в 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 все равно оптимизирует это?

Ответы [ 8 ]

18 голосов
/ 13 февраля 2009

Как насчет возврата ссылки на массив?

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

Редактировать в соответствии с комментарием Дехманна:

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

sub getInfo {
  my @array;
  push @array, 'foo';
  # ...
  return \@array;
}      
13 голосов
/ 13 февраля 2009

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

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

8 голосов
/ 13 февраля 2009

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

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

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

3 голосов
/ 13 февраля 2009

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

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

2 голосов
/ 14 февраля 2009

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

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

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

Это ужасно. Это намного лучше.

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

Он также пригоден для картирования и сопоставления.

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

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

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 ​​.

2 голосов
/ 13 февраля 2009

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

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;

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

0 голосов
/ 25 марта 2016

3 других потенциально БОЛЬШИХ улучшения производительности, если вы читаете весь большой файл и разбиваете его на массивы:

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

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

0 голосов
/ 13 февраля 2009

Я ничего не знаю о Perl, так что это не зависящий от языка ответ.

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

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

...