Generator
Każdy programista w swojej pracy boryka się z problemami związanymi z użyciem pamięci. Czasami urwanie 1mb jest na wagę złota i od tego może zależeć poprawne wykonanie skryptu.
Najczęstszym problemem jest procesowanie tablic zawierających tysiące rekordów. Na szczęście z pomocą przychodzi Generator. Jest to funkcja, która pozwala na iterowanie po zbiorach danych. W PHP został dodany w wersji 5.5. Aby zacząć używać funkcji Generatora wystarczy zamiast return użyć yield.
function getData() { for ($i = 0; $i < 100000; $i++) { yield $i; } } $data = getData();
Funkcja getData() zwraca obiekt klasy Generator, która implementuje interface Iterator.
Dzięki temu tablica nie jest od razu budowana w pamięci, a my mamy możliwość iterowania po rekordach.
Test
Jak wygląda kwestia wydajności? Przeprowadziłem 2 testy. Jedno przy użyciu PDO i tabeli zawierającej ok 200k rekordów oraz z tablicą również zawierającą ok 200k rekordów.
<?php class TestGenerator { private $pdo; public function __construct() { $this->pdo = new PDO( 'mysql:host=127.0.0.1;dbname=test;port=3300', 'root', 'root' ); } public function pdoWithGenerator() { $stmt = $this->pdo->query('select id from `test`'); while ($row = $stmt->fetch()) { yield $row; } echo 'PDO Generator: ' . round(memory_get_usage() / 1024 / 1024, 2) . ' MB' . PHP_EOL; } public function pdoWithoutGenerator() { $stmt = $this->pdo->query('select id from `test`'); $test = []; while ($row = $stmt->fetch()) { $test[] = $row; } echo 'PDO Brak generatora: ' . round(memory_get_usage() / 1024 / 1024, 2) . ' MB' . PHP_EOL; return $test; } public function testArrayWithGenerator() { for ($i = 0; $i <= 200042; $i++) { yield $i; } echo 'Tablica Generator: ' . round(memory_get_usage() / 1024 / 1024, 2) . ' MB' . PHP_EOL; } public function testArrayWithoutGenerator() { $test = []; for ($i = 0; $i <= 200042; $i++) { $test[] = $i; } echo 'Tablica brak generatora: ' . round(memory_get_usage() / 1024 / 1024, 2) . ' MB' . PHP_EOL; return $test; } } $test = new TestGenerator(); foreach ($test->pdoWithGenerator() as $item){} foreach ($test->pdoWithoutGenerator() as $item){} foreach ($test->testArrayWithGenerator() as $item){} foreach ($test->testArrayWithoutGenerator() as $item) {}
Podsumowanie
Wnioski z tego testu są dość oczywiste. Jak widać powyżej, używając Generatora można zaoszczędzić znaczną ilość pamięci.
Ciekawostki:
* Można wykonywać operacje po yield, np. za pomocą break.
function getData() { for ($i = 0; $i < 100000; $i++) { yield $i; if ($i === 10) { break; } } } $data = getData(); foreach ($data as $i) { echo $i. PHP_EOL; }
* Można zwracać pary klucz-wartość.
function getData() { for ($i = 0; $i < 10; $i++) { yield $i => $i+1; } } $data = getData(); foreach ($data as $key => $value) { echo 'Key: ' . $key . PHP_EOL; echo 'Value: ' . $value . PHP_EOL; }
Autorem tekstu jest Łukasz Cieślik.