foreach my $key (keys %hash) { ... }
for my $value (values %hash) { ... }
for
и foreach
являются синонимами в Perl, поэтому, несмотря на тот факт, что ваши два примера фрагментов работают с разными частями хэша, это одно и то же.
Хорошо, вот что происходит внутри: в каждом случае все ключи или все значения рассчитываются как список, а затем циклическая конструкция выполняет итерацию по этому вычисленному списку. Есть внутренняя проверка, но эта проверка только для того, чтобы увидеть, достиг ли цикл смещения последнего элемента в списке. Это дешевая операция в базовом C-коде. Чтобы было ясно, keys
и values
не вызываются на каждой итерации. Список повторяющихся вещей вычисляется только один раз в начале цикла.
Кроме того, $key
и $value
являются псевдонимами фактического ключа или фактического значения, а не копий. Таким образом, копия для каждой итерации не создается.
Нюанс, который часто упускается, заключается в том, что список итераций предварительно вычисляется при входе в цикл. Вот почему это считается ужасной идеей:
foreach my $line (<$file_handle>) {...}
... потому что весь файл должен быть прочитан и сохранен в памяти сразу перед обработкой первой строки. Тот факт, что список должен быть сначала доступным внутри, обычно является приемлемым компромиссом памяти для вещей, которые уже находятся в памяти для начала. Но для внешних источников, таких как файл, нет гарантии, что доступная память может вместить все это - особенно если это какой-то бесконечный поток. Рассмотрим этот код:
open my $fh, '<', '/dev/urandom';
say ord while <$fh>;
Он никогда не завершится, но будет излучать постоянный поток порядковых значений. Тем не менее, он не увеличивается в использовании памяти.
Теперь измените вторую строку следующим образом:
say ord for <$fh>;
Похоже, что он зависает, пока он потребляет всю системную память, пытаясь извлечь все содержимое / dev / urandom (и бесконечного потока). Он должен сделать это, прежде чем сможет начать итерацию, потому что именно так работает цикл foreach на основе диапазона в Perl и некоторых других языках.
Таким образом, цикл foreach, основанный на диапазоне, является недорогим с точки зрения вычислительных затрат, но в некоторых случаях потенциально дорогостоящим в плане использования памяти.
Говоря с вашим последним примером:
my @array = keys %hash;
foreach my $keys (@array) { ... }
Это не имеет заметного значения, и на самом деле может быть медленнее или потреблять больше памяти. Когда я сравниваю два подхода с хешем 100000 элементов, разница между ними составляет всего 2%, или в пределах погрешности:
Rate copy direct
copy 35.9/s -- -2%
direct 36.7/s 2% --
Вот код:
use Benchmark qw(cmpthese);
my %hash;
@hash{1..100000} = (1..100000);
sub copy {
my @array = keys %hash;
my $b = 0;
$b += $_ foreach @array;
return $b;
}
sub direct {
my $b = 0;
$b += $_ foreach keys %hash;
return $b;
}
cmpthese(-5, {
copy => \©,
direct => \&direct,
});