Столкнувшись с мнением специалистов о преимуществах использования генераторов перед обычными массивами, решил разобраться с этим вопросом. По мнению опытных программистов, итерирование массивов потребляет в разы больше ресурсов, чем итерирование генераторов. Когда я об этом услышав впервые, я сам с трудом понимал, что такое генераторы и с чем их едят. Поэтому начнем с понимания вопроса.

Генераторы в PHP - что это такое?

Генератор, по определению, является объектом, выдающим результаты своей деятельности. Такие результаты могут производиться (генерироваться) как единократно, так и с определённым интервалом. В 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.'';
	}

Результат работы

01123581321345589144233377610987159725844181

Да, все настолько просто, что и не стоило длинных описаний. Или стоило? Напоминаю, приведенный пример является одним из самых примитивных. А если рассмотреть работу генератора в тандеме с итераторами, да при использовании с большими массивами данных... Но, признаюсь, я еще сам до этого не совсем дорос, но вижу довольно светлую перспективу.

Сравнение массивов и генераторов

Конечно, заголовок не корректен, но нужно коротко и понятно. Речь пойдет о сравнительном анализе потребляемых машинных ресурсов при обработке данных в классическом массиве и генераторе.

В качестве примера возьму тот же ряд Фибоначчи, но увеличу в нем количество обрабатываемых членов, скажем, до миллиона.

Версия используемой 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
memory (MB): 112.0078125

Обработка данных из генератора


$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, "
";

Fatal error: Allowed memory size of 134217728 bytes exhausted (tried to allocate 77594648 bytes) in /var/www/js-master.ru/content/50.php/Generatory/Generatory.htm on line 158