【问题标题】:PHP foreach performance when adding string to variable将字符串添加到变量时的PHP foreach性能
【发布时间】:2011-12-06 23:50:13
【问题描述】:

有一个如下的foreach:

        foreach($data as $r=>$d)
          {
            $return = $return. "<tr>
            <td>
            ".$d["client_id"]."
            </td>
        ......
            <td>
                ".$d["date_stamp"]."

            </td>
            </tr>";
            }
          } 

这需要我的数据超过 2 秒!进行处理,但是如果我执行以下操作:

      foreach($data as $r=>$d)
        {
          $now= "<tr>
          <td>
          ".$d["client_id"]."
          </td>
      ......
          <td>
              ".$d["date_stamp"]."

          </td>
          </tr>";
          $return = $return.$now;
        } 

只需要0.2秒..

好吧,好吧,你说“好吧,使用第二种方法”,我当然会这样做,但这对我来说是个谜为什么两种方法之间的性能差异如此之大?任何想法好的。。谢谢

添加测试用例:

   //////////function to get time
    function parsemicrotime(){
       list($usec, $sec) = explode(" ",microtime());
       return ((float)$usec + (float)$sec);
       }

    ////////////define test array
    $a = array();
    for($i = 0; $i < 5000; $i ++)//generate 5k rows
      {
        for($k=0; $k<5;$k++)//lets have just 6 columns
          {
            $a[$i]["column_".$k] = 'test string '.$i.' / '.$k.' - note that the size of the $output string makes a huge difference ';
          }
      }

    ///////////////first test
    $time_start = parsemicrotime();
        $output = '';
        foreach($a as $row=>$columns)
          {
            $output = $output ."
            <tr>
              <td>".$columns["test_0"]. "</td>
              <td>" .$columns["test_1"]. "</td>
              <td>" .$columns["test_2"]. "</td>
              <td>" .$columns["test_3"]. "</td>
              <td>" .$columns["test_4"]. "</td>
              <td>" .$columns["test_5"]. "</td>
            </tr>";
          }
    $approach_1_result = parsemicrotime()-$time_start;


    /////////////second test
    $time_start2 = parsemicrotime();
        $output2 = '';
        foreach($a as $row2=>$columns2)
          {
            $now2= "
            <tr>
              <td>".$columns["test_0"]. "</td>
              <td>" .$columns["test_1"]. "</td>
              <td>" .$columns["test_2"]. "</td>
              <td>" .$columns["test_3"]. "</td>
              <td>" .$columns["test_4"]. "</td>
              <td>" .$columns["test_5"]. "</td>
            </tr>";
            $output2 = $output2 .$now2;
          }
    $approach_2_result = parsemicrotime()-$time_start2;


    /////////////third test
    $time_start3 = parsemicrotime();
    ob_start();
        $output3 = '';
        foreach($a as $row3=>$columns3)
          {
            echo "
            <tr>
              <td>".$columns["test_0"]. "</td>
              <td>" .$columns["test_1"]. "</td>
              <td>" .$columns["test_2"]. "</td>
              <td>" .$columns["test_3"]. "</td>
              <td>" .$columns["test_4"]. "</td>
              <td>" .$columns["test_5"]. "</td>
            </tr>";
          }
    $output3 = ob_get_clean();
    $approach_3_result = parsemicrotime()-$time_start3;


    die("first test:".$approach_1_result."<br>second test:".$approach_2_result."<br>third test:".$approach_3_result);

【问题讨论】:

  • 您是否不止一次地经历过这种情况,或者只是一次经历过这种情况?比如,CPU 忙于其他事情,网络速度慢,或者其他什么
  • 因为您实际上做了其他指标。
  • 尽量重现问题。
  • 有多少次迭代?然而:听起来很奇怪,无论如何......
  • 是的,它是二维数组,在 foreach 之前和之后测量时间,用这两种方法尝试了几次,结果 +- 一直相同..使用 echo 更快,但是 echo 和 $return=$return.$new 的区别;是微不足道的(并且添加 ob_start() 等甚至可能会减慢一点)

标签: php performance variables foreach variable-assignment


【解决方案1】:

我使用生成的数组做了一些类似的实验:

$a = [];
for($i = 0; $i < 10000; $i ++)
  $a[] = $i;

为了给它们计时,我只需在执行前存储microtime,并在执行后从microtime 中减去它。我执行了 10 次代码并取平均值。

首先我尝试了类似于您的第一种方法的方法:

$output = '';
foreach($a as $k => $v)
  $output = $output . "some static text" . $v . "some other text";

这记录了~3s 的疯狂时间!然后我使用单引号尝试了相同的代码并得到了相同的结果。

然后我将连接行更改为:

$output .= 'some static text' . $v . 'some other text';

这导致~0.007s 的时间快了~429 倍!

最后我把代码改成:

$output = '';
ob_start();
foreach($a as $k => $v)
  echo 'some static text' . $v . 'some other text';
$output = ob_get_clean();

并且得分略微.=方法慢(仍然是~0.007)。

免责声明:以下内容只是我对时代为何如此的直觉。

现在,我不是 PHP 内部的专家,但我猜第一种方法慢得多的原因是因为它必须创建一个新字符串并复制旧字符串(它正在慢慢地使其~350,000 个字符的最终大小)10,000 次,并且复制通常是一个相当低效的操作。然而,.= 方法只是扩展了原始字符串,避免了复制操作。缓冲方法类似,可能是因为写入输出流的成本与扩展变量的成本相似,ob_startob_get_clean 增加了边际开销。

【讨论】:

  • 我也不是专家,但我了解了 PHP 5.3 的实现。在我看来,这两个运算符都会导致相同的调用:case ZEND_CONCAT: case ZEND_ASSIGN_CONCAT: return (binary_op_type) concat_function; 如果结果和第一个参数相同,此函数可以执行 emallocerealloc。这引出了我的问题:您使用哪个 PHP 版本进行了测试,您能否尝试用更长的文本填充您的数组?这将禁止在 eralloc 中重用内存块。
  • 这是5.4beta1(我无法抗拒短数组语法)。我将在今天晚些时候用更长的文本重新运行测试并发布结果。
【解决方案2】:

这两种方法之间不同,但令人惊讶的是,应该会有如此大的不同。

每次连接两个字符串时,PHP 都必须分配一个新的内存块,足够大以容纳新字符串。对于非常大的字符串,它必须找到更大的连续内存块。因为每次它必须大一点,它就不能重用以前的块(它们太小了)。因此,如果您的字符串增长,查找另一个内存块并复制字符串可能会变得很慢。

  1. 在您的第一个示例中,您在单个循环中有很多 . 操作。随着每一个.,已经很大的字符串变得更大。

  2. 在第二个示例中,您收集循环的所有 .operations。变量 $now 将相对较小,因此这些连接速度很快。每个循环只需要找到一个大内存块。

如前所述,我有点惊讶,它应该会产生如此大的差异,但取决于迭代次数,它是可能的。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2015-09-05
    • 1970-01-01
    • 2017-09-09
    • 1970-01-01
    • 2016-07-14
    • 1970-01-01
    • 2013-11-25
    • 2017-04-03
    相关资源
    最近更新 更多