Производительность локальной переменной и доступа к массиву
Я проводил сравнительный анализ производительности Perl и столкнулся с делом, которое мне показалось странным. Предположим, у вас есть функция, которая использует значение из массива несколько раз. В этом случае вы часто видите некоторый код как:
sub foo {
my $value = $array[17];
do_something_with($value);
do_something_else_with($value);
}
Альтернатива - вообще не создавать локальную переменную:
sub foo {
do_something_with($array[17]);
do_something_else_with($array[17]);
}
Для наглядности первое понятнее. Я предположил, что производительность будет по крайней мере равной (или лучше) и для первого случая - для поиска в массиве требуется умножение-и-сложение, в конце концов.
Представьте мое удивление, когда эта тестовая программа показала обратное. На моей машине повторный поиск в массиве на самом деле быстрее, чем сохранение результата, пока я не увеличу ITERATIONS до 7; другими словами, для меня создание локальной переменной имеет смысл только в том случае, если она используется по крайней мере 7 раз!
use Benchmark qw(:all);
use constant { ITERATIONS => 4, TIME => -5 };
# sample array
my @array = (1 .. 100);
cmpthese(TIME, {
# local variable version
'local_variable' => sub {
my $index = int(rand(scalar @array));
my $val = $array[$index];
my $ret = '';
for (my $i = 0; $i < ITERATIONS; $i ++) {
$ret .= $val;
}
return $ret;
},
# multiple array access version
'multi_access' => sub {
my $index = int(rand(scalar @array));
my $ret = '';
for (my $i = 0; $i < ITERATIONS; $i ++) {
$ret .= $array[$index];
}
return $ret;
}
});
Результат:
Rate local_variable multi_access
local_variable 245647/s -- -5%
multi_access 257907/s 5% --
Это не ОГРОМНОЕ различие, но оно поднимает мой вопрос: почему медленнее создать локальную переменную и кэшировать поиск в массиве, чем снова выполнять поиск? Читая другие SO сообщения, я видел, что другие языки / компиляторы имеют ожидаемый результат, а иногда даже превращают их в один и тот же код. Что делает Perl?
2 ответа
Сегодня я стал больше возиться с этим, и я определил, что скалярное назначение любого рода является дорогостоящей операцией по сравнению с издержками поиска в массиве с одной глубиной.
Кажется, что это только повторяет первоначальный вопрос, но я чувствую, что нашел больше ясности. Если, например, я изменю свою подпрограмму local_variable, чтобы выполнить другое назначение следующим образом:
my $index = int(rand(scalar @array));
my $val = 0; # <- this is new
$val = $array[$index];
my $ret = '';
... код подвергается дополнительному штрафу в 5% скорости по сравнению с версией с одним назначением - даже если он не делает ничего, кроме фиктивного присвоения переменной.
Я также проверил, не вызвал ли область действия установку / демонтаж $var
чтобы снизить производительность, переключив его на глобальный вместо локального. Разница незначительна (см. Комментарии к @zdim выше), указывая на конструкцию / разрушение как на узкое место в производительности.
В конце концов, моя путаница была основана на ошибочных предположениях о том, что скалярное назначение должно быть быстрым. Я привык работать в C, где копирование значения в локальную переменную является чрезвычайно быстрой операцией (1-2 инструкции asm).
Оказывается, это не относится к Perl (хотя я не знаю точно, почему, это нормально). Скалярное присваивание является относительно "медленной" операцией... Что бы ни делали внутренние компоненты Perl, чтобы получить n-й элемент объекта Array, по сравнению с ним, на самом деле, это довольно быстро. "Умножение и сложение", о котором я упоминал в первоначальном посте, все еще гораздо менее трудоемко, чем код для скалярного назначения.
Вот почему требуется так много поисков, чтобы соответствовать производительности кэширования результата: простое назначение переменной "cache" происходит в ~7 раз медленнее (для моей настройки).
Давайте сначала обратимся к следующему утверждению: ожидается, что кэширование поиска будет быстрее, поскольку оно избегает повторных поисков, даже если оно стоит некоторых, и оно начинает работать быстрее, когда выполнено более 7 поисков. Теперь это не так шокирует, я думаю.
Что касается того, почему он медленнее, чем за семь итераций... Я предполагаю, что стоимость создания скаляра все еще выше, чем эти несколько поисков. Это, безусловно, больше, чем один поиск, да? Как насчет двух, тогда? Я бы сказал, что "несколько" вполне может быть хорошей мерой.