【问题标题】:PHPExcel very slow - ways to improve?PHPExcel 很慢 - 改进方法?
【发布时间】:2011-05-12 20:08:46
【问题描述】:

我正在使用 PHPExcel 在 .xlsx 中生成报告。在最初的测试阶段,使用小数据集(数十行,3 张纸)还可以,但现在在每张纸超过 500 行的实际生产数据上使用它时,它变得非常慢。生成一个文件需要 48 秒,并且在运行包含更多信息的报告时,整个事情都会失败并显示 Fatal error: Maximum execution time of 30 seconds exceeded in PHPExcel/Worksheet.php on line 1041。有时它在另一个 PHPExcel 文件中,所以我怀疑确切的位置是否相关。

理想情况下,如果可能的话,我想以某种方式加快速度。如果没有,那么至少增加这个脚本的执行限制。

到目前为止,我看到的唯一建议是在范围内而不是单个单元格中设置样式。不幸的是,我已经在范围内进行了造型,而且它也相当小。还有其他建议吗?

【问题讨论】:

  • 我将这个库用于一个只有 40 行数据的报告,但是非常风格化并且是 2 页宽,所以我有很多列。我的应用程序也很慢。我最终不得不延长 php.ini 中的最大执行时间
  • Excel 有一个非常“简洁”的内部二进制格式,PHPExcel 必须跳很多圈才能将 PHP 数据类型转换为 Excel 的内部表示。除了减少进入电子表格的数据量之外,您无能为力加快速度。
  • @Marc,但这是 Excel 2010 格式,即 XML。根据内存限制错误,我的猜测是 PHPExcel 正在使用 DOM(或更糟的是基于 PHP 的旧 XML 解析器)将 XML 文件加载到内存中。像这样的东西可能应该是使用 XMLReader/XMLWriter 并在操作完成后忘记结果(除了可能是文件中的索引区域)。
  • @Kevin:是的,但即使在 xml 格式中,仍有大量 cdata 部分带有从旧格式继承的旧式二进制 blob。
  • 最终在某些循环的每次迭代中添加 set_time_limit(30) 以延长最大执行时间。

标签: php spreadsheet phpexcel


【解决方案1】:

它是否填充工作表?还是储蓄?你觉得太慢了吗?

您如何使用数据填充电子表格?

  • 使用fromArray() 方法比填充每个单独的单元格更有效,尤其是在您使用高级值绑定器自动设置单元格数据类型时。
  • 如果您使用

    为工作表中的每个单元格设置值
    $objPHPExcel->getActiveSheet()->setCellValue('A1',$x);
    $objPHPExcel->getActiveSheet()->setCellValue('B1',$y);
    

    使用

    $sheet = $objPHPExcel->getActiveSheet();
    $sheet->setCellValue('A1',$x);
    $sheet->setCellValue('B1',$y);
    

    这样您就只能访问getActiveSheet() 方法一次; 或利用流畅的界面设置多个单元格,只需一次调用$objPHPExcel->getActiveSheet()

    $objPHPExcel->getActiveSheet()->setCellValue('A1',$x)
                                  ->setCellValue('B1',$y);
    

您已评论将样式应用于单元格范围:

  • 您还可以选择使用applyFromArray() 一次性设置各种样式设置。
  • 如果您可以将样式应用于列或行而不是简单地应用于范围,则效率会高得多

如果您在工作簿中使用公式,则保存时:

  • 使用

    $objWriter->setPreCalculateFormulas(false)
    

    禁用在 PHPExcel 中计算公式。

这些只是有助于提高性能的一些提示,论坛主题中还有更多建议。它们不一定都有帮助,太多取决于您的特定工作簿以提供任何绝对值,但您应该能够改善这种缓慢的速度。即使是我用于开发的小笔记本也可以比您的生产服务器更快地编写 3 个工作表、20 列、2,000 行 Excel 2007 文件。

编辑

如果可以简单地提高 PHPExcel 本身的速度,我早就这样做了。事实上,我一直在进行性能测试,看看如何提高它的速度。如果您想要比 PHPExcel 本身提供的更快的速度,那么可以使用 list of alternative libraries here

【讨论】:

  • 有没有人注意到单独的$sheet->insertNewRowBefore($row)(在循环内)也会对性能造成一些影响?
  • @javiniar.leonard - insertNewRowBefore() 总是很昂贵,而且当插入到工作表顶部而不是靠近底部时更是如此:始终尽量避免在循环中使用它;您可以指定要插入的行数作为第二个参数,这比单独插入每一行要高效得多
  • 另外xdebug可能是性能瓶颈,我看到激活的时候PHPExcel慢很多。
  • xdebug 会减慢代码执行速度,无论您运行的是什么 PHP,PHPExcel 也不例外
  • 超高速:将 $sheet->insertNewRowBefore($row) 移出循环大大提高了速度,很容易知道行的长度并在循环之前预先填充我的 250 行 22 列文件从 6秒到毫秒。
【解决方案2】:

我也遇到了这个问题。既然这个问题得到了这么多的关注,我想我会投入两分钱。

设置单元格值

不要单独设置每个单元格的值,而是使用fromArray() 方法。取自the wiki.

$arrayData = array(
array(NULL, 2010, 2011, 2012),
array('Q1',   12,   15,   21),
array('Q2',   56,   73,   86),
array('Q3',   52,   61,   69),
array('Q4',   30,   32,    0),
);

$as = $objPHPExcel->getActiveSheet();

$as->fromArray(
    $arrayData,  // The data to set
    NULL,        // Array values with this value will not be set
    'C3'         // Top left coordinate of the worksheet range where
                 //    we want to set these values (default is A1)
);

样式化单元格

静态

为范围应用样式也比单独为每个单元格设置样式更快(注意模式??)。

$default_style = array(
    'font' => array(
        'name' => 'Verdana',
        'color' => array('rgb' => '000000'),
        'size' => 11
    ),
    'alignment' => array(
        'horizontal' => \PHPExcel_Style_Alignment::HORIZONTAL_CENTER,
        'vertical' => \PHPExcel_Style_Alignment::VERTICAL_CENTER
    ),
    'borders' => array(
        'allborders' => array(
            'style' => \PHPExcel_Style_Border::BORDER_THIN,
            'color' => array('rgb' => 'AAAAAA')
        )
    )
);

// Apply default style to whole sheet
$as->getDefaultStyle()->applyFromArray($default_style);

$titles = array(
    'Name',
    'Number',
    'Address',
    'Telephone'
);

$title_style = array(
    'font' => array(
        'bold' => true
    ),
    'fill' => array(
        'type' => \PHPExcel_Style_Fill::FILL_SOLID,
        'startcolor' => array('rgb' => '5CACEE')
    ),
    'alignment' => array(
        'wrap' => true
    )
);

$as->fromArray($titles, null, 'A1'); // Add titles

$last_col = $as->getHighestColumn(); // Get last column, as a letter

// Apply title style to titles
$as->getStyle('A1:'.$last_col.'1')->applyFromArray($title_style);

动态

我使用 PHPExcel 来检查电子表格中给出的数据与数据库中的当前数据。由于每个单元格都是单独检查的,因此我将样式放在一个数组中(null 表示没有样式),并使用下面的循环来获取要应用样式的单元格范围。

/*
 * $row is previously set in a loop iterating through each 
 *     row from the DB, which is equal to a spreadsheet row.
 * $styles = array(0 => 'error', 1 => 'error', 2 => null, 3 => 'changed', ...);
 */
$start = $end = $style = null;
foreach ($styles as $col => $s) {
    if (!$style && !$s) continue;
    if ($style === $s) {
        $end = $col;
    } else {
        if ($style) {
            $array = null;
            switch ($style) {
                case 'changed':
                    $array = $this->changed_style;
                    break;
                case 'error':
                    $array = $this->error_style;
                    break;
                case 'ignored':
                    $array = $this->ignored_style;
                    break;
            }
            if ($array) { 
                $start = \PHPExcel_Cell::stringFromColumnIndex($start);
                $end = \PHPExcel_Cell::stringFromColumnIndex($end);
                $as->getStyle($start.$row.':'.$end.$row)->applyFromArray($array);
            }
        }
        $start = $end = $col;
        $style = $s;
    }
} 

【讨论】:

  • fromArray 快得令人难以置信!
【解决方案3】:

我遇到了同样的问题 - 我试图写入大约 450 行和 11 列数据,并且我一直在运行 30 秒超时。通过批量添加所有新行,然后在事后检查并设置单元格内容,我能够将执行时间缩短到 2 秒或更短。换句话说,我在一次调用 insertNewRowBefore() 中插入 450 行,然后循环遍历这些行并稍后在这些行中设置内容。

像这样:

$num_rows = count($output_rows);
$last_row = $sheet->getHighestRow();
$row = $last_row + 1;
$sheet->insertNewRowBefore($row, $num_rows);
// Now add all of the rows to the spreadsheet
foreach($output_rows as $line) {
    $i = 0;
    foreach($line as $val) {
        // Do your setCellValue() or setCellValueByColumnAndRow() here
        $i++;
    }
    $row++;
}

【讨论】:

  • 我实际上从不显式添加行,除了在输入所有数据后添加的几个标题行,只是为了简化计算数据单元格坐标。
  • 我用->insertNewRowBefore() 尝试了你的建议。我在两张纸上写了大约 6000 行。不使用 ->insertNewRowBefore 的循环 ->setCellValue 耗时 151 秒,使用 ->insertNewRowBefore 耗时 147 秒。很不幸,我无法确认您的解决方案:-(
【解决方案4】:

我绝不是使用 PHPExcel 的专家,但 OfficeOpenXML 格式(*.xlsx 文件的格式)本身就是一组以 *.xlsx 扩展名打包在 ZIP 存档中的 XML 文件 .如果你重视你的表现并且知道你将传递什么样的数据,也许最好构建自己的 XLSX 生成器,精简到最重要的功能,也许在数据库层进行一些计算等等,而不是解析整个文档。

为此,您可以从分析使用较小数据集生成的文件开始(通过将扩展名从 *.xlsx 更改为 *.zip,解压缩并浏览单个文件的内容)。这样您就可以确定您真正需要的内容并自己生成(通过创建适当的 XML 文件并将它们打包到 ZIP 存档中,然后重命名为具有 *.xlsx 扩展名)。

还有OfficeOpenXML的规格,很大(几千页),所以除非你真的想看,否则我不建议阅读。创建文件以匹配它们由 PHPExcel 生成的方式就足够了。

上面提到的解决方案不包括任何与PHPExcel相关的技巧,因为我不是这方面的专家。不过,我之前一直对 OOXML 标准化过程很感兴趣,如果有关此标准的知识能帮助您解决问题,我会很高兴。

【讨论】:

  • 那是相当硬核。实际上,我在文件中的计算方面并没有做太多事情,它是大多数数据表示,带有一些非常小的基本计算。从短期来看,我增加了执行时间限制,从长远来看,可能会考虑使用自定义引擎或迁移到 csv 文件。
  • 我喜欢 Atlassian JIRA 解决它的方式 - JIRA 只是保存一个名为 .xls 的 HTML 文档,Excel 能够很好地打开和显示它。渲染 HTML 是如此快速和直接。不过有一个警告 - 这样的 HTML 文件会使 Excel 在打开时警告格式不匹配。
【解决方案5】:

对于包含 a - amj (~800) 列且只有 ~50 行的 XLSX 导出,我也遇到了 30 秒的边界。为了测试我的程序,我将处理的行数限制为 7,这在 25 秒内工作。

  1. 从单个 $objPHPExcel->getActiveSheet() 到 $sheet(第一个建议)实际上将有限行的时间从 25 秒增加到 26 秒。

  2. 真正帮助我的是用一个在 PHP 中递增的简单 $column_nr 变量替换我的所有 getHighestDataColumn(),我从 26 秒变为 7 秒。

之后,我能够在 11 秒内处理所有 50 行。

【讨论】:

    【解决方案6】:

    我以前从未见过的一个性能提示与添加工作表有关,或者更具体地说,是设置工作表的标题。如果您添加许多工作表,则操作顺序可能会产生巨大影响。对于以下测试,我使用了一个包含 120 个填充工作表的电子表格,并计算了创建另外 120 个空工作表所需的时间。

    首先,使用the docs中显示的步骤:

    for ($i = 0; $i < 120; $i++) {
        $sheet = $spreadsheet->createSheet();
        $sheet->setTitle('Sheet Title' . $i);
    }
    // Time: 12.5605s
    

    第二种,来自the docs的替代方法:

    for ($i = 0; $i < 120; $i++) {
        $sheet = new Worksheet($spreadsheet, 'Sheet Title' . $i);
        $spreadsheet->addSheet($sheet);
    }
    // Time: 0.0266s
    

    上述两种方法之间的大部分性能差距可以通过使用setTitle 的第二个参数来缩小(如果在您的情况下这样做是安全的;请参阅the docs):

    for ($i = 0; $i < 120; $i++) {
        $sheet = $spreadsheet->createSheet();
        $sheet->setTitle('Sheet Title' . $i, false);
    }
    // Time: 0.5793s
    

    【讨论】:

      【解决方案7】:

      我遇到了完全相同的问题。得到一个 5000 行、32 列的 CSV 文件,需要很长时间才能处理。事实证明,几乎所有花费在“处理”上的时间实际上都是字符编码,默认情况下它被设置为将所有内容编码为 UTF8。因此,如果您进入 config\excel.php 文件并向下滚动到编码,只需将其设置为:

      /*
      |--------------------------------------------------------------------------
      | Import encoding
      |--------------------------------------------------------------------------
      */
          'encoding' => array(
      
              'input'  => '',
              'output' => ''
      
          ),
      

      仅此一项 - 处理上述文件大约需要 8 秒。不过,您可能希望警告您的客户正确保存 CSV。

      【讨论】:

        【解决方案8】:

        就我而言,我通过将缓存存储方法更改为内存 gzip cache_in_memory_gzip

        来提高性能
        $cm = \PHPExcel_CachedObjectStorageFactory::cache_in_memory_gzip;
        \PHPExcel_Settings::setCacheStorageMethod($cm);
        

        【讨论】:

          猜你喜欢
          • 2018-03-03
          • 2021-08-30
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2013-04-24
          • 2019-02-25
          • 1970-01-01
          • 1970-01-01
          相关资源
          最近更新 更多