@asdasdqwe

Какая разница между генераторами и массивами?

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


Допустим перебираем 1млн записей
Почему генераторы потребляют меньше памяти, чем массивы?
Какая разница через генератор или через обычный массив. У нас 1 млн записей и их все равно надо где-то хранить
  • Вопрос задан
  • 1647 просмотров
Решения вопроса 3
FanatPHP
@FanatPHP
Чебуратор тега РНР
Ну собственно именно то, что написано.
"Генератор позволяет" не значит "генератор гарантированно сэкономит память, ужав миллион строк в одну". Это значит, что если есть возможность не создавать массив в памяти перед тем как перебирать его значения, то можно написать функцию-генератор, результат которой будет выглядеть как массив, но при этом будет выдавать по одному значению. Это возможно в таких случаях, как генерация значений на лету или чтение данных из внешнего источника.

То есть фактически это просто красивая обертка над обычным циклом, которая может представить его как "перебор массива". Эта обертка нам будет нужна, если по каким-то причинам мы хотим обращаться к результатам цикла, как к массиву.

То есть надо чётко понимать, что "экономит память" генератор только по сравнению с созданием полноценного массива.

Там где вместо создания массива можно обойтись циклом, можно использовать и генератор. Но можно и не использовать - код будет прекрасно работать и без него.
Там, где реально нужен массив, например с произвольным доступом или для накопления результатов вычислений, генератор превращается в тыкву
Ответ написан
@alexalexes
https://www.php.net/manual/ru/language.generators....
foreach (xrange(1, 9, 2) as $number) {
    echo "$number ";
}

Если рассказать простым языком магии, то при таком вызове внутри foreach, функция xrange определяет, что ее результат не собираются записывать в отдельную переменную, а собираются перебирать по элементам.
Поэтому, когда выполнение интерпретатора php доходит до команды as, функция xrange выполняется до первого попавшегося yield внутри этой функции и возвращает это значение. При этом, контекст функции запоминается, а также внутренний цикл:
for ($i = $start; $i <= $limit; $i += $step) {
            yield $i;
}

засыпает до того момента, пока во внешнем foreach выполнение не пройдет команду as еще раз, тогда произойдет размораживание yield и извлечение следующего значения, чтобы использовать его только как один элемент $number.
За счет того, что as меняет режим работы вызова функции xrange, получается не извлекать все подряд.
Ответ написан
Комментировать
profesor08
@profesor08 Куратор тега PHP
Это разные вещи. Массив - структура данных. Генератор - функция.
Ответ написан
Комментировать
Пригласить эксперта
Ответы на вопрос 2
@Garry_Galler
Почему генераторы потребляют меньше памяти, чем массивы?

Простой ответ.

Потому что генератор не хранит результат своих промежуточных вычислений в памяти.
Массив это структура данных, которая хранит все свои значение в памяти, последовательно и безапелляционно :-)

Но генератор это и не структура данных, чтобы как-то напрямую их сравнивать.
Генератор это паттерн программирования, который может быть реализован как особая функция.
Очевидно, что реализация генератора должна иметь некоторые предпосылки. Нельзя просто так взять и перестать пользоваться массивами и писать только генераторы.
Когда у нас есть возможность (или настоятельная необходимость) не считывать все данные (сеть, диск) полностью в ОЗУ, а забирать их чанками (порциями), чтобы обработать, отправить результат в вызывающую функцию и тут же забыть его, вот тогда стоит подумать о возможности реализации своего генератора\итератора, если таковой не предоставляется библиотекой языка.

Сложный ответ (для тех, кто умеет читать много букв)

Генератор это подвид итератора. Итератор в контексте программирования это поведенческий паттерн, описанный в знаменитой книге "банды четырех". Его суть - в предоставлении "возможности последовательно обходить элементы составных объектов, не раскрывая их внутреннего представления."

Итераторы в разных языках программирования как правило представлены специальным интерфейсом (или протоколом), который нужно реализовать для создания собственных итераторов.

Генератор это частный случай итератора, который, как правило, создается функцией возвращающей объект итератора. Для упрощения создания генераторов языки программирования предоставляют программистам специальное ключевое слово yield, которое должно использоваться вместо return (либо совместно с ним). yield создает итератор под капотом, позволяя не реализовывать весь протокол Итератора вручную.

Справка из википедии:
Одним из способов реализации итераторов является использование сопроцедур, которые могут возвращать управление (и вычисленные результаты) несколько раз, запоминая своё состояние и точку возврата в предыдущем вызове. В некоторых языках сопроцедуры могут быть представлены особого вида функциями, называемыми генераторами. Генератор — функция, которая помнит, в каком месте был предыдущий return (yield), и при следующем вызове возобновляет работу с прерванного места
.

Еще одно определение генераторов:
Генератор — это объект, который сразу при создании не вычисляет значения всех своих элементов.
Он хранит в памяти только последний вычисленный элемент, правило перехода к следующему и условие, при котором выполнение прерывается.
Вычисление следующего значения происходит лишь при выполнении метода next(). Предыдущее значение при этом теряется.
Этим генераторы отличаются от списков — те хранят в памяти все свои элементы, и удалить их можно только программно. Вычисления с помощью генераторов называются ленивыми, они экономят память


Суть работы yield одинакова для всех ЯП.
Вот описание принципа работы yield для языка C# ( вы же все знаете Джона Скита? - главного эксперта stackoverflow и автора известных книг), где это ключевое слово появилось довольно давно (в 2005-м, в Python -2001-м, а вообще - в 1975 году) и многие другие ЯП со временем переняли его.

При наличии оператора yield return внутри метода компилятор строит на основе данного метода конечный автомат. Для реализации итератора конечный автомат обладает следующими свойствами:

  1. Он имеет некое начальное состояние
  2. При вызове MoveNext() выполняется код из метода GetEnumerator() по тех по, пока не будет достигнут оператор yield return;
  3. Когда используется свойство Current, он возвращает последнее выданное значение.
  4. Он должен знать, когда выдача значений завершена, чтобы метод MoveNext() мог возвратить false;



Генераторы как подкласс итераторов имеют огромное значение для так называемых "ленивых вычислений".

Они позволяют создавать (вычислять) значения на лету и тем самым экономить память, в отличие от "энергичных вычислений".
Таким образом, на основе генераторов можно реализовать создание бесконечных последовательностей, чтение из бесконечного потока, чтение данных не вмещающихся в ОЗУ компьютера, ленивые вычисления, конвейерную обработку данных (можно создавать целые pipelines из генераторов), асинхронные корутины и т .д.

P.S. Генераторы не имеют никакого отношения к циклам (цикл это только лишь один из способов обхода генераторных коллекций - извлечения значений из них ). Также генераторы не являются никаким синтаксическим сахаром ("красивой оберткой для цикла"), как было написано в других ответах. Синтаксических сахаром является исключительно само ключевое слово yield. При выполнении кода с yield он компилируется в Finite State Machine .
Ответ написан
Разница не в использовании памяти в принципе. А в использовании оперативной памяти, которой меньше.
Рассмотрим кейс: у нас есть набор записей общим объемом 1ТиБ на винте, надо перебрать все и посчитать, скажем, среднее.
Если у нас есть 1ТиБ свободной оперативы - мы можем целиком загрузить все туда и прямым доступом перебрать массив.
Но у нас вряд ли есть столько оперативы. В таком случае мы можем считывать данные блоками размером с доступную оперативную память, выполнять действие, выгружать блок и загружать следующий.
Генераторы же - синтаксический сахар для таких операций. Можно написать функцию-генератор, которая будет при каждом вызове возвращать следующий элемент. Но не хранить все их в оперативе. Отсюда и экономия памяти, но именно оперативной.
Ответ написан
Ваш ответ на вопрос

Войдите, чтобы написать ответ

Похожие вопросы