KorniloFF-ScriptS ©

Здравствуйте, Гость!
Page
Menu

Генераторы

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

Генераторы в 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)#17 (0) { } . То есть вызванная функция нам вернула объект. Не находите, что такое поведение очень сходно с поведением функций в javascript?

Как нам теперь использовать такой генератор? Если вы уже знакомы с итераторами (впоследствии напишу вводную статью и о них), то догадались, что результат работы такой функции можно обрабатывать в цикле.

Характерной особенностью такого использования является то, что цикл будет работать не с копией, как в случае использования массивов, а непосредственно с самим итератором, что значительно сокращает объем потребляемой памяти. В конце статьи я приведу пример-сравнение.

Использование генератора

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


	foreach(xFibonachi () as $d) {
		echo ''.$d.'';
	}

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

01123581321345589144233377610987159725844181

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

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

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

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

Версия используемой PHP - 7.0.25

Обработка данных из массива


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.4899
memory (MB): 75.12890625

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


$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, "
";
time: 0.3847
memory (MB): 82.25

Сравнение результатов

И вот тут, уважаемые господа, наступает ступор, у меня, во всяком случае. Это как??? Обработка массива показывает лучшие результаты, чем генератор?

Похоже, да. И всё дело в версии PHP. Сейчас, на момент написания, я тестирую на 7.1, на сервере хостинга у меня стоит 7.0 (посмотрим, что она еще покажет). Когда я переходил на семёрку, я, конечно читал о том, что основной задачей её выпуска было увеличение производительности, но не думал, что до такой степени.

Выводы и заключение

Все тезисы, представленные в статье, имеют место быть при использовании PHP ниже 7.0. В таких версиях генераторы позволяют уменьшить потребляемые ресурсы на порядок. В версиях 7+, как ни странно, выигрывает обработка массивов.

Тем не менее, не считаю, что зря написал эту статью. Ведь, во-первых, еще многие пользуются PHP-5, во вторых, всё же знакомство с генераторами состоялось, а в-третьих, я смог прийти к тем выводам, что пришел. Теперь каждый, кто хочет проверить скорость отработки данных из массива или генератора, может скопировать коды из этой статьи и тестировать их у себя.

Ну, а кто не склонен к экспериментаторству и любит использовать массивы, может вздохнуть спокойно. Создатели РНР-7 потрудились на славу и переходить на генераторы стало излишне.

Позже постараюсь потестировать работу генераторов в связке с итераторами, возможно, там будут другие результаты.

Комментарии к теме (0)

Комментариев пока нет.

Дабавить комментарий