2010年7月19日月曜日

PHPExcelのメモリ馬鹿食いについて

===============
2010-10-20追記
関連POST
PHPExcel1.7.4の不具合?について
===============

PHPExcelのシート追加でメモリをガンガン消費しちゃう件。
リンク先を参考に解析。

SE奮闘記: 【PHP】PHPExcelがループ内でメモリを使いすぎる

ワーク
シート数
メモリ使用量(Byte)
改善前 改善後
1 21,233,664 21,233,664
2 22,544,384 22,544,384
3 25,165,824 23,855,104
4 30,408,704 24,903,680
5 40,632,320 25,952,256
6 60,293,120 27,262,976
7 100,139,008 28,311,552
8 180,092,928 29,360,128
9 340,000,768(推定) 30,408,704
10 659,816,448(推定) 31,457,280

改善前

$aSheet = $this->excel->getActiveSheet();
for ($i = 0; $i < count($this->data); $i++) {
    if ($i != 0) {
        $this->excel->addSheet(clone $aSheet);
    }
}

改善後

$aSheet = $this->excel->getActiveSheet();
$newSheets = array();
for ($i = 0; $i < count($this->data); $i++) {
    if ($i != 0) {
        $newSheets[] = clone $aSheet;
    }
}
foreach ($newSheets as $newSheet) {
    $this->excel->addSheet($newSheet);
}

改善前は大体次の数式のようにメモリ使用量が増えていってるっぽくてO(2^n)のオーダー(かな?)
f(n+1) = 2.5MB * 2^(n+1) + f(n)
改善後は次の数式でO(n)のオーダー。
f(n) = 1.25MB * n + 20MB

リンク先で触れられていた「シートをコピーして追加する」の処理をやめて「テンプレートからシートを流し込むとよい」という内容は確認できませんでした。
リンク先とは状況が違うとは思いますが、こちらの解析ではPHPExcel_Worksheetを__cloneする時にメモリがどんどん消費されていっているらしくて、PHPExcel_Worksheet#copy()も内部的には__cloneを呼んでいる。

だからと言って、
$this->excel->addSheet(clone $aSheet);

$newSheets[] = clone $aSheet;
になったことでそんなに劇的に変わる理由がよく分かりませんが、
$aSheet = $this->excel->getActiveSheet();
した$aSheetが指しているactiveなシートの参照先が
$this->excel->addSheet(clone $aSheet);
したことによってどうにかなってしまったんではなかろうか、と想像。

PHPExcelの実装の問題なのか、cloneする変数の参照先の親オブジェクトにループ内でメンバ変数追加するとそうなってしまうPHPの仕様なのか、何とも言えませんが、今後の為にメモ。

3 件のコメント:

  1. ありがとうございます!
    詳細の共有感謝致します。

    返信削除
  2. ありがとうございます、大変助かりました。

    返信削除