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

StackOverflow https://stackoverflow.com/questions/546175

Вопрос

У меня часто есть подпрограмма в 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 все равно оптимизирует это?

Это было полезно?

Решение

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

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

Другие советы

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

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

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

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

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

Отвечая на последний вопрос: нет, Perl не оптимизирует это.На самом деле это невозможно, потому что возврат массива и возврат скаляра — это принципиально разные вещи.

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

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

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;

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

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

Это самая легкая часть.Вторым фактором, который часто упускают из виду, является интерфейс.Как будет использоваться возвращенный массив?Это важно, потому что разыменование всего массива в 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() вернуть ссылку на массив в скалярном контексте и массив в контексте списка.Это запутывает традиционное использование скалярного контекста в качестве длины массива, что удивит пользователя.

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

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

Это делается с помощью магии Данные::Псевдоним.

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

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

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

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

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

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

Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top