Следующие блоки запускают цикл, присваивая тему переменной $var
:
my $var;
находится вне циклаmy $var;
находится внутри циклаstate $var;
находится внутри циклаmy $limit=10_000_000;
{
my $var;
for ^$limit { $var =$_; }
say now - ENTER now;
}
{
for ^$limit { my $var; $var=$_; }
say now - ENTER now;
}
{
for ^$limit { state $var; $var=$_; }
say now - ENTER now;
}
Примерная длительность вывода (в секундах) каждого блока следующая:
0.5938845
1.8251226
2.60700803
Переменные движения https://docs.perl6.org/syntax/состояниеstate
имеют ту же лексическую область видимости, что и my
. Функционально блок кода 1 и блок 3 обеспечивают одно и то же постоянное хранилище для нескольких вызовов соответствующего блока цикла.
Почему state
(и внутренняя my
) версия занимает так много времени? Что еще он делает?
Редактировать:
Подобно комментарию @HåkonHægland, если я вырежу и вставлю приведенный выше код, чтобы запустить каждый блок три раза, время значительно изменится для my $var
вне цикла (первый случай):
0.600303
1.7917011
2.6640811
1.67793597
1.79197091
2.6816156
1.795679
1.81233942
2.77486777
Я получаю сильно различающиеся результаты в зависимости от порядка блоков, см.: tio.run/##K0gtyjH7/…
Согласно Хокону, одной из проблем может быть неиспользование чего-то вроде крун. Aiui: первый показанный блок назначает новый Scalar
вне цикла и, таким образом, экономит затраты на создание нового Scalar
при каждом проходе через цикл, который предположительно связан со вторым показанным блоком. И оба этих блока можно оптимизировать до простого цикла, тогда как третий требует создания замыкания, что дороже. Я с нетерпением жду авторитетного ответа от смелого разработчика, такого как timotimo или jnthn...
@HåkonHægland, это действительно странная разница во времени. Я тоже добавлю это в вопрос.
Краткая версия: в мире без какой-либо оптимизации времени выполнения (специализация типов, JIT и т. д.) тайминги соответствовали бы вашим ожиданиям. На время здесь влияет то, насколько хорошо оптимизатор обрабатывает каждый пример.
Во-первых, интересно запускать код без какой-либо оптимизации во время выполнения. В моей (довольно медленной) виртуальной машине на компьютере, на котором я сейчас нахожусь, вставка MVM_SPESH_DISABLE=1
в среду приводит к следующим таймингам:
13.92366942
16.235372
14.4329288
Они имеют какой-то интуитивный смысл:
Scalar
каждый раз в цикле, что составляет дополнительное время.state
. Переменная state
хранится в объекте кода замыкания, а затем копируется в кадр вызова во время входа. Это дешевле, чем каждый раз выделять новый Scalar
, но все же немного больше работы, чем вообще не выполнять эту операцию.Далее запустим 3 программы с включенным оптимизатором, каждый пример в своей изолированной программе.
0.86298831
, в 16 быстрее. Вперёд оптимизатор! Он имеет встроенное тело цикла.1.2288566
, в 13 раз быстрее. Тоже не слишком хлипкий. Он снова встроил тело цикла. (Этот случай также станет намного дешевле в будущем, как только анализатор побегов станет достаточно умным, чтобы исключить выделение Scalar
.)2.0695035
, в 7 раз быстрее. Это сравнительно не впечатляет (даже если все же значительное улучшение), и основная причина в том, что он не встраивает тело цикла. Почему? Потому что он еще не умеет встраивать код, использующий переменные состояния. (Как это увидеть: запустить с MVM_SPESH_INLINE_LOG=1
в среде, и среди вывода: Can NOT inline (1) with bytecode size 78 into (3): cannot inline code that declares a state variable
.)Короче говоря, доминирующим фактором здесь является встраивание тела цикла с переменными состояния, что в настоящее время невозможно.
Не сразу понятно, почему оптимизатор работает хуже в случае с внешним объявлением $var
, когда это не первый цикл в программе; это больше похоже на ошибку, чем на разумный случай «эта функция еще недостаточно оптимизирована». В своей небольшой защите ему все же удается добиться значительного улучшения, даже если оно не такое большое, как хотелось бы!
Нет времени для каждого блока