Столкнувшись с мнением специалистов о преимуществах использования генераторов перед обычными массивами, решил разобраться с этим вопросом. По мнению опытных программистов, итерирование массивов потребляет в разы больше ресурсов, чем итерирование генераторов. Когда я об этом услышав впервые, я сам с трудом понимал, что такое генераторы и с чем их едят. Поэтому начнем с понимания вопроса.
Генератор, по определению, является объектом, выдающим результаты своей деятельности. Такие результаты могут производиться (генерироваться) как единократно, так и с определённым интервалом. В PHP суть генератора есть функция. Такая функция, по своей конструкции, особенно не отличается от любой другой. Но среди её особенностей можно сразу выделить две: функция-генератор, в большинстве случаев, содержит в себе какой-либо из видов цикла, а также способна возвращать результат своей работы не единократно, как обычная (return), а многократно (yield).
Если быть более точным, то можно сказать, что функция-генератор возвращает объект, содержащий все результаты её работы, и обладающий интерфейсом Traversable. А если проще - это объект-итератор. Подобные объекты способна обрабатывать внутренняя функция foreach().
Во всем остальном генератор не отличим от функции. Он также может принимать аргументы и оперировать с ними, его также нужно инициализировать, etc.
В качестве примера простейшего генератора можно рассмотреть функцию, генерирующую математический ряд Фибоначчи, в котором первые два числа равны 0 и 1, а каждое последующее число равно сумме двух предыдущих. Поскольку сам ряд бесконечен, будем генерировать его первые 20 членов.
function xFibonachi () {
$f=0; $s=1; $ss=0;
yield $f;
yield $s;
for ($i=0; $i < 18; $i++) {
$ss= $f + $s;
$f= $s;
$s= $ss;
yield $ss;
};
}
Или сделаем его параметрическим, установив количество членов в качестве переменной
function xFibonachi ($c=20) {
$f=0; $s=1; $ss=0;
yield $f;
yield $s;
for ($c-=2; $c--;) {
$ss= $f + $s;
$f= $s;
$s= $ss;
yield $ss;
};
}
Итак, мы получили функцию-генератор. Как это проверить? Можно просто ввести команду var_dump(xFibonachi ());, в результате выполнения которой мы получим object(Generator)#19 (0) {
}
. То есть вызванная функция нам вернула объект. Не находите, что такое поведение очень сходно с поведением функций в javascript?
Как нам теперь использовать такой генератор? Если вы уже знакомы с итераторами (впоследствии напишу вводную статью и о них), то догадались, что результат работы такой функции можно обрабатывать в цикле.
Характерной особенностью такого использования является то, что цикл будет работать не с копией, как в случае использования массивов, а непосредственно с самим итератором, что значительно сокращает объем потребляемой памяти. В конце статьи я приведу пример-сравнение.
Поскольку основные принципы работы я описал выше, привожу сам код подключения генератора и результаты его работы.
foreach(xFibonachi () as $d) {
echo ''.$d.'';
}
Да, все настолько просто, что и не стоило длинных описаний. Или стоило? Напоминаю, приведенный пример является одним из самых примитивных. А если рассмотреть работу генератора в тандеме с итераторами, да при использовании с большими массивами данных... Но, признаюсь, я еще сам до этого не совсем дорос, но вижу довольно светлую перспективу.
Конечно, заголовок не корректен, но нужно коротко и понятно. Речь пойдет о сравнительном анализе потребляемых машинных ресурсов при обработке данных в классическом массиве и генераторе.
В качестве примера возьму тот же ряд Фибоначчи, но увеличу в нем количество обрабатываемых членов, скажем, до миллиона.
Версия используемой PHP - 7.4.16
function Fibonachi ($c) {
$f=0; $s=1; $ss=0; $arr=[];
$arr[]= $f;
$arr[]= $s;
for ($c-=2; $c--;) {
$ss= $f + $s;
$f= $s;
$s= $ss;
$arr[]= $ss;
}
return $arr;
}
$start_time=microtime(true);
$result = '';
foreach(Fibonachi (1e6) as $d) {
$result .= ''.$d.'';
}
$end_time=microtime(true);
echo "time: ", bcsub($end_time, $start_time, 4), "
";
echo "memory (MB): ", memory_get_peak_usage(true)/1024/1024, "
";
time: 0.2871
$start_time=microtime(true);
$result = '';
foreach(xFibonachi (1e6) as $d) {
$result .= ''.$d.'';
}
$end_time=microtime(true);
echo "time: ", bcsub($end_time, $start_time, 4), "
";
echo "memory (MB): ", memory_get_peak_usage(true)/1024/1024, "
";