「copy on write」這個特性的意思是,在 variable 在沒有異動的時候,不會在記憶體中 re-allocate 一個新的區塊來存放資料,只有在被更新、修改或刪除資料時,才會另外將異動過的資料存放到記憶體其他區域。
剛看到時自己寫不知道這是什麼神奇的東西,看實例比較快。
先建立一個超級大的檔案 huge.log:
$content = file_get_contents('huge.log');
// 818.4 MB
echo sprintf("%.1f MBn", memory_get_peak_usage() / 1024 / 1024);
建立一個 function,這個 function 什麼事都不做,直接回傳我們傳進去的 $content:
$content = file_get_contents('huge.log');
function reply($content)
{
return $content;
}
reply($content);
// 818.4 MB
echo sprintf("%.1f MBn", memory_get_peak_usage() / 1024 / 1024);
well … 我以前原本以為 pass by value,會另外將 variable clone 一份供 function 內部使用,但上面的 script 中呼叫了 reply() 以後,記憶體用量並沒有增加,所以將變數傳進 function 時,並沒有 clone 一份新的出來。
將原本的 reply() 稍做調整,在 return 之前新增一點東西到 $content 中:
function reply($content)
{
return $content . 'some more';
}
reply($content);
// 1636.6 MB
echo sprintf("%.1f MBn", memory_get_peak_usage() / 1024 / 1024);
reply() 中對 $content 加了一些新的資料,這時候最大記憶體使用量就變為原來的 2 倍,很明顯 PHP 在記憶體 alloc 新的區塊來存放被修給過的 $content。所以「copy on write」的意思,就是這個資料被 write 時,PHP 才會要新的記憶體 (copy) 來存放有被修改過的資料。
處理大型資料時,可以利用這個特性來節省記憶體使用量。
例如:decorator() 這個 function 要對一個大型陣列的資料做處理:
function decorator($data)
{
$sum = 0;
foreach ($data as $item) {
$sum += $item;
}
$data['average'] = $sum / count($data);
$data['sum'] = $sum;
return $data;
}
上面的 function 因為會修改 $data 的內容,所以 PHP 會 clone 一個 $data 再來做修改。
改成以下的寫法:
function decorator($data)
{
$sum = 0;
foreach ($data as $item) {
$sum += $item;
}
$change = [
'average' => $sum / count($data),
'sum' => $sum,
];
return $change;
}
foreach (decorator() as $key => $val) {
$data[$key] = $val;
}
在 decorator() 並不會動到 $data 的內容,所以不需要 clone $data,而是在 function 執行結束以後,直接對 $data 做修改。這樣只需要多儲存 $change 陣列,和後續的處理動作,而不會讓使記憶體用量 double。