Возвращает весь массив из подпрограммы 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. Но некоторые языки предоставляют программисту возможность выбрать, какой подход.