2016年10月9日 星期日

Generators 產生器 in php

        今天學到了一個新技術:產生器(Generators)。

        一句話來表示的話,產生器就是簡單版的迭代器,往常在設計一個function去做計算的時候,都會去令一個回傳變數,運算完把值賦予給回傳變數之後,再return 回傳變數,這樣一來,如果回傳變數非常大,那麽會佔用非常可觀的記憶體空間。

        於是產生器應運而生,不但可以做到完全相同的事,而且每次只會佔用到非常微小的記憶體空間,每一次經歷迭代的時候,php才會讓產生器計算出下一個迭代的值,以下來實作範例:


function makeRange($length) {
    $dataset = array();
    for ($i = 0; $i < $length; $i++) {
        $dataset[] = $i;
    }

    return $dataset;
}

$customRange = makeRange(1000000);
foreach ($customRange as $i) {
    echo $i, PHP_EOL;
}



        這邊的dataset會被配置一百萬個記憶體空間,如果使用產生器來實作的話會變成:


function makeRange($length) {
    for ($i = 0; $i < $length; $i++) {
        yield $i;
    }
}

$customRange = makeRange(1000000);
foreach ($customRange as $i) {
    echo $i, PHP_EOL;
}



        一樣的結果,但是在任何時刻只會配置到一組記憶體空間

        每一次的foreach才會讓產生器產出一個數值,產出之後會在當下暫停,在被呼叫下一個數值的時候恢復運作,一直到函式結束或沒有回傳值的return才會結束。


        另外,產生器還有另一個驚喜的作用:迭代串流來源!

        假設我們必須在一個只有1GB 可用記憶體的VPS裡讀取一個4GB的CSV檔,沒有辦法將整個檔案全部裝進記憶體中,這個時候就可以使用產生器的特性了:

function getRows($file) {
    $handle = fopen($file, 'rb');
    if ($handle === false) {
        throw new Exception();
    }
    while (feof($handle) === false) {
        yield fgetcsv($handle);
    }
    fclose($handle);
}

foreach (getRows('data.csv') as $row) {
    print_r($row);
}



        每一次的foreach呼叫產生器取值,而每一次的取值只會用到print_r所呼叫的記憶體,而不是先將整整4GB的讀進記憶體裡,再印出來。



        Ps. PHP_EOL的用法類似\n,都是換行。

參考資料:

https://www.tenlong.com.tw/items/9863477788?item_id=1007177
http://blog.ircmaxell.com/2012/07/what-generators-can-do-for-you.html

沒有留言:

張貼留言