Реальная работа с yield (PHP)


Некоторые функции PHP могут новичкам показаться надуманными, излишними и не встречающимися в реально разработке. Одной из таких являются генераторы. Есть ли какое-либо преимущество в производительности при использовании yield по сравнению с возвратом нового массива в функции/методе?

Вообще, я читал книгу по C#, и одна из концепций, касалась интерфейса IEnumerable, и он использовал yield в методе, отвечающем за обход объекта. Читая, я начал думать о ключевом слове yield и внезапно вспомнил, что PHP также использует yield.

Первоначально во всех моих функциях и методах PHP, отвечающих за обход и возврат массива, я делал что-то вроде этого:

function gen_one_to_three () {
    $ arr = массив ();
    for ($ i = 1; $ i <= 3; $ i ++) {
        $ arr [] = $ i;
    }
    return $ arr;
}
Тогда как с yield функция будет выглядеть так:

function gen_one_to_three () {
    for ($ i = 1; $ i <= 3; $ i ++) {
        yield $ i;
    }
}
Имейте в виду, я знаю, что это, вероятно, чрезмерное упрощение yield и не может точно передать его полезность. Во всяком случае, сама по себе функция совершенно бесполезна, но я в основном использую ее в качестве примера, чтобы проиллюстрировать два общих подхода к одной и той же проблеме.

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

Но в случае улучшения производительности, достаточно ли этого, чтобы гарантировать изменение личных привычек за 5 лет? По вашему опыту, может ли что-то подобное потенциально ухудшить читаемость? Есть ли какие-то основные проблемы, которые я мог бы упустить из-за общей важности - или отсутствия таковой - ключевого слова yield?

Это наиболее полезно в ситуациях, когда $i станет действительно очень большим. Массивы в php, ну, давайте просто не будем говорить, что это «легковесные» структуры данных. Если вы поместите баджиллион вещей в массив, чтобы вернуть его, вы очень быстро раздуваете свою память. Yield в этом очень помогает.

Для массивов не будет большой разницы в производительности, если только массив не будет большим.

Вот более практичный пример.

Допустим, у вас есть функция, которая абстрагирует строки чтения из CSV. Он читает из CSV, захватывает заголовки, а затем возвращает ассоциативный массив.

Так что-то вроде этого:

function readCSV($file){
	$fh = fopen($file,'r');
	$headers = fgetcsv($fh);
	$lines = Array();
	while($line = fgetcsv($fh)){
		$vars = Array();
		foreach($line as $key=>$val){
			$vars[$headers[$key]] = $val;
		}
		$lines[] = $vars;
	}
	return $lines;
}
Перебирает файл, помещает все строки в ассоциативный массив, возвращает массив.

Но что, если я ищу только одну запись? Что, если эта запись находится в верхней части CSV-файла на 10 000 строк? Если я вызываю readCSV и получаю весь массив, я прочитаю весь файл, когда мне нужна только одна строка, и все готово.

Вместо этого я могу:

function readCSV($file){
	$fh = fopen($file,'r');
	$headers = fgetcsv($fh);
	while($line = fgetcsv($fh)){
		$vars = Array();
		foreach($line as $key=>$val){
			$vars[$headers[$key]] = $val;
		}
		yield $vars;
	}
}
foreach(readCSV($file) as $vars){
	if($vars['username'] == 'JoeCoT'){
		echo "This is what \n";
		break;
	}
}
Теперь я могу просто перебирать строки, пока не найду то, что ищу, а затем остановиться.

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

Это также значительно упрощает абстрагирование при работе с такими вещами, как наборы результатов SQL-запросов, поскольку вы можете заставить свою функцию-оболочку вызывать команду sql fetch в цикле с yield, вместо того, чтобы буферизовать все результаты в массив.


public function selectRows($sql) {
    $result = $this->query($sql);
    while ($row = $result->fetch_assoc()) {
      yield $row;
    }
  }
  
 
foreach ($db->selectRows($sql) as $row) {
  
}


function mysqli_gen (mysqli_result $res)
{
    while($row = mysqli_fetch_assoc($res))
    {
        yield $row;
    }
}
Но он может позволить вам замаскировать последовательность while()/fetch() как вызов foreach(), если вы предпочитаете, без дополнительных затрат памяти.

Однако PDO — не очень хороший пример, поскольку PDOStatement уже реализует проходимый интерфейс и, следовательно, его можно перебирать с помощью foreach():

$res = $mysqli->query("SELECT * FROM users");
foreach (mysqli_gen($res) as $row)
{
    var_export($row);
}


function mysqli_gen (mysqli_result $res)
{
    while($row = mysqli_fetch_assoc($res))
    {
        yield $row;
    }
}
Автор этого материала - я - Пахолков Юрий. Я оказываю услуги по написанию программ на языках Java, C++, C# (а также консультирую по ним) и созданию сайтов. Работаю с сайтами на CMS OpenCart, WordPress, ModX и самописными. Кроме этого, работаю напрямую с JavaScript, PHP, CSS, HTML - то есть могу доработать ваш сайт или помочь с веб-программированием. Пишите сюда.

тегизаметки, php, yield




Отправляя сообщение я подтверждаю, что ознакомлен и согласен с политикой конфиденциальности данного сайта.




Урок 21. Логические операторы JavaScript
Адаптация табличной верстки
Имена хостов