【问题标题】:PHP Loop - expression/function causing serious delayPHP Loop - 表达式/函数导致严重延迟
【发布时间】:2011-04-01 11:44:53
【问题描述】:

我想知道是否有人可以阐明这个问题.. PHP 5.3.0 :)

我有一个循环,它正在获取 CSV 文件(大,200mb)的内容,处理数据,为 mysql 插入构建变量堆栈,一旦循环完成并创建变量,我将插入信息。

现在首先,mysql 插入执行得很好,没有延迟,一切都很好,但是延迟是 LOOP 本身,我最初使用 fgetcsv() 来读取 CSV 文件,但与 file_get_contents() 相比,这有严重延迟 - 所以我切换到 file_get_contents()。循环将在几秒钟内执行,直到我尝试添加一个函数(我还在循环内添加了没有该函数的表达式以查看它是否有帮助)以使用每行的 CSV 数据创建一个数组,这个是什么导致解析时间严重延迟! (基于这个 200mb 文件,差异约为 30 秒,但我猜这取决于 csv 文件的文件大小)

这里有一些代码,你可以看到我在做什么:

$filename = "file.csv";
$content = file_get_contents($filename);    
$rows = explode("\n", $content);    
foreach ($rows as $data) {    
    $data = preg_replace("/^\"(.*)\"$/","$1",preg_split("/,(?=(?:[^\"]*\"[^\"]*\")*(?![^\"]*\"))/", trim($data))); //THIS IS THE CULPRIT CAUSING SLOW LOADING?!?
}

运行上面的循环,几乎可以立即执行而无需行:

$data = preg_replace("/^\"(.*)\"$/","$1",preg_split("/,(?=(?:[^\"]*\"[^\"]*\")*(?![^\"]*\"))/", trim($data)));

我也尝试过创建如下函数(循环外):

function csv_string_to_array($str) {
$expr="/,(?=(?:[^\"]*\"[^\"]*\")*(?![^\"]*\"))/";
$results=preg_split($expr,trim($str));
return preg_replace("/^\"(.*)\"$/","$1",$results);
}

并调用函数而不是一个衬垫:

$data = csv_string_to_array($data);

再次没有运气:(

任何帮助将不胜感激,我猜测 fgetcsv 函数基于它导致的延迟以非常相似的方式执行,循环并从数据行创建一个数组。

丹尼

【问题讨论】:

  • 您是否尝试过使用“for”循环而不是“foreach”?
  • 已经尝试比较整个时间,直到您将数据放入您的方法与 fgetcsv() 的数组中? preg_replace 和 preg_split 是重量级函数,因为基于正则表达式处理字符串是一项 cpu 密集型任务。将函数调用放入另一个函数不能加快速度
  • 我认为fgetcsv 会比那更快
  • 艾默曼+1。为什么不能使用 fgetcsv(),danny,它专门用于读取/解析 CSV 数据? php.net/fgetcsv
  • @Wallgate - for 循环实际上比 foreach 慢,因为它需要更多的脚本操作码来处理计数器和条件。

标签: php performance loops


【解决方案1】:

正则表达式子表达式(以“(...)”为界)是问题所在。很容易证明将这些添加到表达式中会大大降低其性能。我要尝试的第一件事是停止使用 preg_replace() 来简单地删除前导和尾随双引号(trim() 将是一个更好的选择),看看这有多大帮助。之后,您可能需要尝试非正则表达式的方式来解析该行。

【讨论】:

    【解决方案2】:

    我部分找到了解决方案,我正在发送一个批处理,一次只循环 1000 行(php 循环 1000 直到它到达文件末尾)。

    然后我只是设置:

    $data = preg_replace("/^\"(.*)\"$/","$1",preg_split("/,(?=(?:[^\"]*\"[^\"]*\")*(?![^\"]*\"))/", trim($data)));
    

    在 1000 行上,因此不会为导致问题的整个文件设置它。

    它现在循环并在 1-2 秒内将 1000 行插入到 mysql 数据库中,对此我很满意。我已经将脚本设置为循环 1000 行,记住它的最后一个位置,然后循环到下一个 1000 直到它到达末尾,它似乎工作正常!

    【讨论】:

      【解决方案3】:

      我想说主要的罪魁祸首是 preg_split() 正则表达式的复杂性。 而且explode() 可能会吃掉几秒钟。

      $content = file_get_contents($filename);    
      $rows = explode("\n", $content); 
      

      可以替换为:

      $rows = file ($filename); // returns an array
      

      但是,我赞同 ITroubs 的上述建议,fgetcsv() 可能是一个更好的解决方案。

      【讨论】:

        【解决方案4】:

        我建议使用 fgetcsv 来解析数据。似乎记忆可能是你最大的影响。因此,为了避免消耗 200MB 的 RAM,您应该按如下方式逐行解析:

        $fp = fopen($input, 'r');
        
        while (($row = fgetcsv($fp, 0, ',', '"')) !== false) {
            $out = '"' . implode($row, '", "') . '"';  // quoted, comma-delimited output
            // perform work
        }
        

        或者:在 preg 中使用条件通常非常昂贵。有时使用 explode()trim() 及其 $charlist 参数来处理这些行会更快。

        另一种选择,如果你还想使用preg,添加S修饰符来尝试加速表达式。

        http://www.php.net/manual/en/reference.pcre.pattern.modifiers.php
        S
        当一个模式要被多次使用时,值得花更多时间分析它以加快匹配时间。如果设置了此修饰符,则执行此额外分析。目前,研究一个模式只对没有单个固定起始字符的非锚定模式有用。

        【讨论】:

          【解决方案5】:

          顺便说一句,我不认为您的函数正在做您认为应该做的事情:当您退出循环时,它实际上不会修改 $rows 数组。为此,您需要更多类似的东西:

          foreach ($rows as $key => $data) {
              $rows[$key]=preg_replace("/^\"(.*)\"$/","$1",preg_split("/,(?=(?:[^\"]*\"[^\"]*\")*(?![^\"]*\"))/", trim($data)));
          

          【讨论】:

            猜你喜欢
            • 2013-11-29
            • 1970-01-01
            • 2018-10-27
            • 2017-08-07
            • 1970-01-01
            • 1970-01-01
            • 2020-09-23
            • 2014-04-24
            • 1970-01-01
            相关资源
            最近更新 更多