iT邦幫忙

0

請問PHP生成器的使用方式

php
  • 分享至 

  • xImage

看到PHP生成器可以減少記憶體消耗,想說來試一下
但結果好像跟我的理解不太一樣

情況1:使用PDO從資料庫內select資料列出來(資料庫是sql server)
比較了2個簡單的情況,類似於

$conn = new PDO("sqlsrv:...",'user','pwd');
$stmt = $conn->query("SELECT * FROM table");
while ($row = $stmt->fetch(PDO::FETCH_ASSOC)) {
    print_r($row);
}

function generatefunc(PDOStatement $stmt): \Generator {
    while ($row = $stmt->fetch(PDO::FETCH_ASSOC)) {	
        yield $row;
	}
}
	
$conn = new PDO("sqlsrv:...",'user','pwd');
$stmt = $conn->query("SELECT * FROM table");
foreach (generatefunc($stmt) as $row) {
	print_r($row);
}

上面是沒使用生成器的情況,下面是用生成器的情況
但測試結果發現,兩種的使用記憶體量是相同的
本來以為是資料量太小,用不完預設的記憶體大小
但把資料量加大之後,反而是使用生成器的情況先出現out of memory的錯誤
不使用生成器則什麼事都沒發生

情況2:使用phpspreadsheet讀取一個xlsx檔案
不使用生成器

$reader = new \PhpOffice\PhpSpreadsheet\Reader\Xlsx();
$reader->setReadDataOnly(true);
$spreadsheet = $reader->load("data.xlsx");
$worksheet = $spreadsheet->getActiveSheet();
	
foreach ($worksheet->getRowIterator() as $row) {
    $cellIterator = $row->getCellIterator();
    foreach ($cellIterator as $cell) {
        print_r($cell->getValue());
    }
}

使用生成器

function generatefunc(): \Generator {
    $reader = new \PhpOffice\PhpSpreadsheet\Reader\Xlsx();
    $reader->setReadDataOnly(true);
    $spreadsheet = $reader->load("data.xlsx");
    $worksheet = $spreadsheet->getActiveSheet();
		
    foreach ($worksheet->getRowIterator() as $row) {
        $cellIterator = $row->getCellIterator();
        foreach ($cellIterator as $cell) {
            yield $cell;
        }
    }
}

foreach (generatefunc() as $cell) {
    print_r($cell->getValue());
}

結果也是兩邊的情況一樣,小檔時兩邊的記憶體用量相同
大檔時兩邊一起out of memory

我想可能是我對生成器的理解有誤
想請問大家,是甚麼情況下,使用PHP生成器才有減少記憶體用量的效果?
謝謝

froce iT邦大師 1 級 ‧ 2024-07-01 13:17:43 檢舉
你要比較當然是用最簡單,沒有外部函式去比較...
通常都會用費式數列這種無窮產出的經典例子去比較。
https://www.php.cn/zh-tw/faq/587131.html

生成器通常是用來惰性求值,白話一點就是運算一個數值,再去處理(儲存)一個數值。
當然,如果你也是運算完通通都存進一個array裡,那佔用記憶體大小還是一樣。
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中
0
㊣浩瀚星空㊣
iT邦大神 1 級 ‧ 2024-07-01 13:01:27
最佳解答

嗯~~~如果依資料庫來看的話。
確實會參數對應應用的問題存在。

再目前依讀取資料庫的資料變數用法。大多是分成兩種

常見的有將所有的資料集轉存到一個陣列上。
之後的資料處理才foreach這個陣列來處理。
但說到這邊,也就是當資料筆數一多的話。自然就會佔用很大的記憶體。
畢竟陣列就是一個變數。將所有資料存到陣列變數內,自然就會佔用。

另一種方式,就目前來說比較少人見到這樣的開發。
就是並不是一口氣將所有資料都先倒出來變數內。
而是一列一列取資料後。處理完成後。再拋棄重新用新的變數接值。
不過這種方式,是需要一直連線資料庫上。
且有時並不太容易統合處理。
程式也會比較不容易寫。所以除非資料大到一定程度的專案。
才會有機會看到這樣的寫法。

像如下的寫法,就是使用第一種方式的做法。

while ($row = $stmt->fetch(PDO::FETCH_ASSOC)) {
    print_r($row);
}

$row就是單純取得單筆資料。但取得下一筆就會重新再給與資料到 $row 上。
並不會累加上去。

而這邊你比較誤會了一件事。生成器的寫法應用。
大多數來說,生成器一定都是用第一種方式的。
但有少部份的生成器也是有如上的應用方式。只是寫法不同。
畢竟,如果要用第二種方式來做的話。就不太可能再將資料存到變數內。
而是取出單筆資料後處理完,才再進行下一筆。
這再程式開發寫作的方法就得有些不同。

現在有相應而成的ORM做法出現。
採用的是第一種及第二種的合并應用原理。

不過我這就不細談了。

總之,結論並非是生成器的問題。
而是你不太了解原理的關係。誤會了生成器的應用方式。
當然,有些生成器,確實沒有第二種的方式。但大多數還是有的。

0
riverdm
iT邦見習生 ‧ 2024-06-30 08:18:09

會 out of memory 應該是在這一句
$spreadsheet = $reader->load("data.xlsx");
這裡的效果應該是把檔案裡的資料一次全讀進記憶體中
所以在你的程式裡面再怎麼用 generator 也不會減少記憶體的使用

用 xlsx 可能比較不好解釋,但如果用 csv 的話也許比較好想像

想像一下,你寫了一個程式。一次把整個 csv 檔讀到記憶體中。再進行處理
跟,你一行一行的讀取,並讀完一行後,馬上進行處理

你可以想像的到,第一種的處理方式所使用的記憶體會是整個檔案的大小。
但第二種,就只會有一行的記憶體用量而已

所以,用 yield 並不會幫你減少記憶體用量,而是你採取那一種方式來處理你的資料才是重點。

0
海綿寶寶
iT邦大神 1 級 ‧ 2024-06-30 14:26:59

參考這篇古文

我要發表回答

立即登入回答