看到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生成器才有減少記憶體用量的效果?
謝謝
嗯~~~如果依資料庫來看的話。
確實會參數對應應用的問題存在。
再目前依讀取資料庫的資料變數用法。大多是分成兩種
常見的有將所有的資料集轉存到一個陣列上。
之後的資料處理才foreach這個陣列來處理。
但說到這邊,也就是當資料筆數一多的話。自然就會佔用很大的記憶體。
畢竟陣列就是一個變數。將所有資料存到陣列變數內,自然就會佔用。
另一種方式,就目前來說比較少人見到這樣的開發。
就是並不是一口氣將所有資料都先倒出來變數內。
而是一列一列取資料後。處理完成後。再拋棄重新用新的變數接值。
不過這種方式,是需要一直連線資料庫上。
且有時並不太容易統合處理。
程式也會比較不容易寫。所以除非資料大到一定程度的專案。
才會有機會看到這樣的寫法。
像如下的寫法,就是使用第一種方式的做法。
while ($row = $stmt->fetch(PDO::FETCH_ASSOC)) {
print_r($row);
}
$row就是單純取得單筆資料。但取得下一筆就會重新再給與資料到 $row 上。
並不會累加上去。
而這邊你比較誤會了一件事。生成器的寫法應用。
大多數來說,生成器一定都是用第一種方式的。
但有少部份的生成器也是有如上的應用方式。只是寫法不同。
畢竟,如果要用第二種方式來做的話。就不太可能再將資料存到變數內。
而是取出單筆資料後處理完,才再進行下一筆。
這再程式開發寫作的方法就得有些不同。
現在有相應而成的ORM做法出現。
採用的是第一種及第二種的合并應用原理。
不過我這就不細談了。
總之,結論並非是生成器的問題。
而是你不太了解原理的關係。誤會了生成器的應用方式。
當然,有些生成器,確實沒有第二種的方式。但大多數還是有的。
會 out of memory 應該是在這一句
$spreadsheet = $reader->load("data.xlsx");
這裡的效果應該是把檔案裡的資料一次全讀進記憶體中
所以在你的程式裡面再怎麼用 generator 也不會減少記憶體的使用
用 xlsx 可能比較不好解釋,但如果用 csv 的話也許比較好想像
想像一下,你寫了一個程式。一次把整個 csv 檔讀到記憶體中。再進行處理
跟,你一行一行的讀取,並讀完一行後,馬上進行處理
你可以想像的到,第一種的處理方式所使用的記憶體會是整個檔案的大小。
但第二種,就只會有一行的記憶體用量而已
所以,用 yield 並不會幫你減少記憶體用量,而是你採取那一種方式來處理你的資料才是重點。