PHP
Excel
PHPExcel

LibXL PHP Extensionでエクセルファイル作成。なにかと重いPHPExcelと比べてメモリ使用量、処理時間が激減

概要

PHPでエクセルファイルを作成するという要件が挙がり、PHPExcelを使ってみたところ、処理時間・メモリ使用量の両面で出力することができず困っていました・・・。

そんな時に見えた一筋の光!LibXLと、PHP Extension(拡張モジュール)に関するお話です。

PHPExcelについて

そもそもPHPExcelとは

PHPでエクセルファイルを生成できるライブラリ。無料で利用でき、簡単な記述で様々な形式のエクセルファイルを作ることができるため、様々なサービスで使われています。

ちなみに個人的にも、当初、PHPでエクセルファイルを生成するなら、PHPExcelの一択と考えていました。

メリット

  • 無料
  • ライブラリを配置して読み込むだけで使える
  • 記述が簡単
  • テンプレートファイルの読み込み可

デメリット

  • とにかく重い
  • 出力に時間がかかる
  • メモリ使用量が膨大
  • メモリ使用量が膨大
  • メモリ使用量が膨大
  • メモリ使用量が膨...

多少お金がかかっても、これらのデメリットを解消したい!!

LibXL PHP Extensionについて

概要

LibXLライブラリと、それをPHPで利用可能にするExtensionです。

エクセルファイルを作成・編集できるLibXLというC++のライブラリがありますが、これはPHP向けのライブラリではありません。が、PHPの開発者でもある Ilia Alshanetskyさんが、PHP向けのExtensionを作成してくださっています!

すでに実務でも何度か使っていますが、省メモリかつ処理も早いので、とても重宝しています。

メリット

  • 出力が早い
  • 省メモリ
  • 記述が簡単
  • テンプレートファイルの読み込み可

デメリット

  • 拡張モジュールなので、PHPの再構築が必要
  • 有料(1ライセンス 199ドル, 無制限版 1,399ドル)

わりとリーズナブル!(と、思っています)
ちなみに、ライセンス未購入の場合は、1行目と所々のセルに、購入を促すテキストが強制的に挿入されます。

LibXL PHP Extension 導入手順

  1. LibXLをダウンロードして展開

    cd /usr/local/src/
    wget http://libxl.com/download/libxl-lin-3.6.5.tar.gz
    tar zxf libxl-lin-3.6.5.tar.gz
    cp libxl-3.6.5.0/lib64/libxl.so /usr/local/lib64/
    
  2. PHP Extensionをダウンロードして、configure

    cd /usr/local/src/
    git clone https://github.com/iliaal/php_excel.git
    cd php_excel
    phpize
    ./configure --with-libxl-incdir=../libxl-3.6.5.0/include_c/ --with-libxl-libdir=/usr/local/lib64/
    make
    make install
    
  3. php.iniに、モジュール読み込みとライセンス情報を追記

    [PHPEXCEL]
    extension=excel.so
    excel.license_name=LICENSE_NAME
    excel.license_key=LICENSE_KEY
    

PHPExcelと、LibXL PHP Extensionの比較

比較内容

  • 100列 × 10,000行 に 1 を入力して出力
  • 1000行単位で、時間とメモリ使用量を計測
  • 環境
    • サーバー:1CPU 1024MB
    • PHP 5.6、メモリは256MB

記述

PHPExcel

セル書き出し以外は、ほぼオフィシャルのサンプルのままです。

<?php

ini_set('memory_limit', '256M');

$time_start = microtime(true);
$memory_start = memory_get_usage();

error_log("START " . debug($time_start, $memory_start));

require_once('./Classes/PHPExcel.php');
require_once('./Classes/PHPExcel/IOFactory.php');

$objPHPExcel = new PHPExcel();

$objPHPExcel->setActiveSheetIndex(0);
$objSheet = $objPHPExcel->getActiveSheet();


// セルに値を入れる
for ($row = 1; $row <= 10000; $row++) {
    for ($col = 0; $col < 100; $col++) {
        $objSheet->setCellValueByColumnAndRow($row , $col, 1);
    }

    // 経過時間、最大消費メモリ量を表示
    if($row % 1000 === 0) {
        error_log("row : {$row}, " . debug($time_start, $memory_start));
    }
}


// Redirect output to a client’s web browser (Excel2007)
header('Content-Type: application/vnd.openxmlformats-officedocument.spreadsheetml.sheet');
header('Content-Disposition: attachment;filename="test.xlsx"');
header('Cache-Control: max-age=0');
// If you're serving to IE 9, then the following may be needed
header('Cache-Control: max-age=1');

// If you're serving to IE over SSL, then the following may be needed
header ('Expires: Mon, 26 Jul 1997 05:00:00 GMT'); // Date in the past
header ('Last-Modified: '.gmdate('D, d M Y H:i:s').' GMT'); // always modified
header ('Cache-Control: cache, must-revalidate'); // HTTP/1.1
header ('Pragma: public'); // HTTP/1.0

$objWriter = PHPExcel_IOFactory::createWriter($objPHPExcel, 'Excel2007');
$objWriter->save('php://output');

error_log("END " . debug($time_start, $memory_start));


function debug($time_start, $memory_start) {
    // 経過時間
    $time_now = microtime(true);
    $time = sprintf('%.5f', ($time_now - $time_start));

    // 消費メモリ
    $memory = sprintf('%.5f', (memory_get_peak_usage() - $memory_start) / (1024 * 1024));

    return "time : {$time}, memory : {$memory}";
}

LibXL PHP Extension

<?php

ini_set('memory_limit', '256M');

$time_start = microtime(true);
$memory_start = memory_get_usage();

error_log("START " . debug($time_start, $memory_start));

$objExcel = new ExcelBook(null, null, true);
$objSheet = $objExcel->addSheet('test');


// セルに値を入れる
for ($row = 1; $row <= 10000; $row++) {
    for ($col = 0; $col < 100; $col++) {
        $objSheet->write($row - 1, $col, 1, null, ExcelFormat::AS_NUMERIC_STRING);
    }

    // 経過時間、最大消費メモリ量を表示
    if(($row) % 1000 === 0) {
        error_log("row : {$row}, " . debug($time_start, $memory_start));
    }
}


header('Content-Type: application/octet-stream');
header('Content-Disposition:attachment;filename="test.xlsx"');
$objExcel->save('php://output');

error_log("END " . debug($time_start, $memory_start));


function debug($time_start, $memory_start) {
    // 経過時間
    $time_now = microtime(true);
    $time = sprintf('%.5f', ($time_now - $time_start));

    // 消費メモリ
    $memory = sprintf('%.5f', (memory_get_peak_usage() - $memory_start) / (1024 * 1024));

    return "time : {$time}, memory : {$memory}";
}

結果

行数 PHPExcel LibXL PHP Extension
開始 0.00001 sec 0.01159 MB 0.00001 sec 0.01244 MB
1,000行 2.39863 sec 53.23675 MB 0.14919 sec 0.01244 MB
2,000行 4.12784 sec 103.74940 MB 0.30125 sec 0.01244 MB
3,000行 6.00642 sec 163.27768 MB 0.45477 sec 0.01244 MB
4,000行 8.40790 sec 204.77458 MB 0.65964 sec 0.01244 MB
5,000行 10.63976 sec 246.33411 MB 0.83599 sec 0.01244 MB
6,000行 - - 1.06977 sec 0.01244 MB
7,000行 - - 1.26355 sec 0.01244 MB
8,000行 - - 1.44006 sec 0.01244 MB
9,000行 - - 1.63398 sec 0.01244 MB
10,000行 - - 1.81274 sec 0.01244 MB
終了 - - 4.28283 sec 0.02690 MB

PHPExcelでは、メモリが足りなくなってしまい、ファイルが出力できませんでした。一方のLibXL PHP Extensionは、ほとんどメモリを使わず、しかも短時間で処理が完了しています。ちなみに、セルのフォーマットも細かく指定できます。

プライベートでの利用やボリュームが少ないエクセルファイルの書き出し程度であれば、無料のPHPExcelを使うという選択も有りだと思いますが、そうでなければLibXL PHP Extensionを選ぶメリットのほうが大きいと思います。