虽然每个都被弃用,但Foreach更换需要花费更多时间

Nik*_*a 웃 12 php each performance foreach while-loop

使用PHP 7.2,each不推荐使用.文件说:

警告此功能自PHP 7.2.0起已废弃.非常不鼓励依赖此功能.

我正在努力调整电子商务应用程序并将所有while-each循环转换为(假设)等效foreach.

正如您在下面看到的,我已经用等效的替换了所有的reset&while循环foreach.

它工作得很好.但是,我们有一个客户在她的购物车中有很长的商品列表,试图结帐并抱怨她从服务器收到错误502.我试图重现它,发现只有她的购物车失败,加载结帐页面需要2分钟,然后加载502次.然后我开始调试我最近修改过的很多文件,试用和错误,直到我发现问题出在这个特定文件和特定功能上.每当我将第一个foreach循环切换回while循环时,客户可以在不到一秒的时间内加载结帐页面.切换回foreach- 再次需要几分钟,但PHP在结束执行之前超时.

我做的是,输出执行过程中的测试foreachVS的while循环(var_dump $products_id$this->contents例如),他们似乎都相同.我已经重写了代码以使其顺利运行并保持PHP 7.2兼容,但我仍然无法弄清楚为什么会发生这种情况.

这是完整的功能:

function get_content_type() {
  $this->content_type = false;

  if ( (DOWNLOAD_ENABLED == 'true') && ($this->count_contents() > 0) ) {

    // reset($this->contents);
    // while (list($products_id, ) = each($this->contents)) {
    foreach(array_keys($this->contents) as $products_id) {

      if (isset($this->contents[$products_id]['attributes'])) {
        // reset($this->contents[$products_id]['attributes']);
        // while (list(, $value) = each($this->contents[$products_id]['attributes'])) {
        foreach ($this->contents[$products_id]['attributes'] as $value) {
          $virtual_check_query = tep_db_query("select count(*) as total from " . TABLE_PRODUCTS_ATTRIBUTES . " pa, " . TABLE_PRODUCTS_ATTRIBUTES_DOWNLOAD . " pad where pa.products_id = '" . (int)$products_id . "' and pa.options_values_id = '" . (int)$value . "' and pa.products_attributes_id = pad.products_attributes_id");
          $virtual_check = tep_db_fetch_array($virtual_check_query);

          if ($virtual_check['total'] > 0) {
            switch ($this->content_type) {
              case 'physical':
                $this->content_type = 'mixed';

                return $this->content_type;
                break;
              default:
                $this->content_type = 'virtual';
                break;
            }
          } else {
            switch ($this->content_type) {
              case 'virtual':
                $this->content_type = 'mixed';

                return $this->content_type;
                break;
              default:
                $this->content_type = 'physical';
                break;
            }
          }
        }

      } elseif ($this->show_weight() == 0) {
      // reset($this->contents);  
      //  while (list($products_id, ) = each($this->contents)) { 
        foreach (array_keys($this->contents) as $products_id) {
          $virtual_check_query = tep_db_query("select products_weight from " . TABLE_PRODUCTS . " where products_id = '" . $products_id . "'");
          $virtual_check = tep_db_fetch_array($virtual_check_query);
          if ($virtual_check['products_weight'] == 0) {
            switch ($this->content_type) {
              case 'physical':
                $this->content_type = 'mixed';

                return $this->content_type;
                break;
              default:
                $this->content_type = 'virtual';
                break;
            }
          } else {
            switch ($this->content_type) {
              case 'virtual':
                $this->content_type = 'mixed';

                return $this->content_type;
                break;
              default:
                $this->content_type = 'physical';
                break;
            }
          }
        }

      } else {
        switch ($this->content_type) {
          case 'virtual':
            $this->content_type = 'mixed';

            return $this->content_type;
            break;
          default:
            $this->content_type = 'physical';
            break;
        }
      }
    }
  } else {
    $this->content_type = 'physical';
  }

  return $this->content_type;
}
Run Code Online (Sandbox Code Playgroud)

谢谢

编辑:这是阵列:https: //pastebin.com/VawX3XpW

该问题已在我尝试的所有配置上进行测试和复制:

1)高端窗口10 pc + WAMP(Apache 2.4 + MariaDB 10.2 + PHP 5.6 +/7 +/7.1 +/7.2 +)

2)高端CentOS/cPanel服务器+ Litespeed + MariaDB 10.1 + PHP 5.6+

只是要强调,我不是要重写代码或模拟each然后重写代码,因为我们不会从中学到很多东西.我只是想找到一个合乎逻辑的解释或解决/调试这个谜的方法.也许有人在某个地方碰到了这样一个问题,并且可以对此有所了解.

更新01/Aug/2018

我一直试图调试这几天,最终发现了一些有趣的东西.我添加了"回声点"并exit在第一个foreach循环和while循环上添加如下:

function get_content_type() {
  $this->content_type = false;

  if ( (DOWNLOAD_ENABLED == 'true') && ($this->count_contents() > 0) ) {

    // reset($this->contents);
    // while (list($products_id, ) = each($this->contents)) { echo '1 ';
    foreach(array_keys($this->contents) as $products_id) { echo '1 ';

      if (isset($this->contents[$products_id]['attributes'])) { echo '2 ';
        // reset($this->contents[$products_id]['attributes']);
        // while (list(, $value) = each($this->contents[$products_id]['attributes'])) {
        foreach ($this->contents[$products_id]['attributes'] as $value) { echo '3 ';
          $virtual_check_query = tep_db_query("select count(*) as total from " . TABLE_PRODUCTS_ATTRIBUTES . " pa, " . TABLE_PRODUCTS_ATTRIBUTES_DOWNLOAD . " pad where pa.products_id = '" . (int)$products_id . "' and pa.options_values_id = '" . (int)$value . "' and pa.products_attributes_id = pad.products_attributes_id");
          $virtual_check = tep_db_fetch_array($virtual_check_query);

          if ($virtual_check['total'] > 0) {
            switch ($this->content_type) {
              case 'physical':
                $this->content_type = 'mixed'; echo '4 ';

                return $this->content_type;
                break;
              default:
                $this->content_type = 'virtual'; echo '5 ';
                break;
            }
          } else {
            switch ($this->content_type) {
              case 'virtual':
                $this->content_type = 'mixed'; echo '6 ';

                return $this->content_type;
                break;
              default:
                $this->content_type = 'physical'; echo '7 ';
                break;
            }
          }
        }

      } elseif ($this->show_weight() == 0) {
      // reset($this->contents);  
      //  while (list($products_id, ) = each($this->contents)) { 
        foreach (array_keys($this->contents) as $products_id) {
          $virtual_check_query = tep_db_query("select products_weight from " . TABLE_PRODUCTS . " where products_id = '" . $products_id . "'");
          $virtual_check = tep_db_fetch_array($virtual_check_query);
          if ($virtual_check['products_weight'] == 0) {
            switch ($this->content_type) {
              case 'physical':
                $this->content_type = 'mixed'; echo '8 ';

                return $this->content_type;
                break;
              default:
                $this->content_type = 'virtual'; echo '9 ';
                break;
            }
          } else {
            switch ($this->content_type) {
              case 'virtual':
                $this->content_type = 'mixed'; echo '10 ';

                return $this->content_type;
                break;
              default:
                $this->content_type = 'physical'; echo '11 ';
                break;
            }
          }
        }

      } else {
        switch ($this->content_type) {
          case 'virtual':
            $this->content_type = 'mixed'; echo '12 ';

            return $this->content_type;
            break;
          default:
            $this->content_type = 'physical'; echo '13 ';
            break;
        }
      }
    } exit; //Exiting from the loop to check output
  } else {
    $this->content_type = 'physical';
  }

  return $this->content_type;
}
Run Code Online (Sandbox Code Playgroud)

当我运行循环时while,我得到的输出只是"1 13"一次,这意味着循环只运行一次并停止.然而,当我把它更改为时foreach,我得到了一个很长的列表"1 13 1 13 1 13 ...",这意味着它循环多次.我去了进一步调查,如果有什么区别breakswhile循环和foreach循环,我还是没能找到任何证明资料.然后我重写了最后break;break 2;和测试foreach一次,这一次它似乎已经只是一次运行,就像当它是一个while用循环break;(不break 2;) 编辑:只是为了澄清-有,而没有任何区别breakS和的foreach break秒.他们以同样的方式工作.

更新#2: 我已修改} elseif ($this->show_weight() == 0) {} elseif (2 == 0) {,while循环现在运行循环次数foreach. var_dump($this->show_weight());结果float 4466.54.这个问题对我来说仍然没有任何意义.

再次感谢

rla*_*vin 15

这实际上是一个非常简单的算法问题,它与以下事实有关show_weight():0您何时循环同一个数组(编辑:并根据您的注释,show_weight()本身循环同一个数组).

TL; DR随着while所有这些袢共享相同的内部指针和影响对方.随着foreach每个回路是独立的,因此其运行方式更多的迭代,因此性能问题.

由于一个例子胜过千言万语,希望以下代码能让事情变得更加清晰:

<?php

$array = ['foo','bar','baz'];

foreach ( array_keys($array) as $key ) {
    echo $array[$key],"\n";
    foreach ( array_keys($array) as $key ) {
        echo "\t",$array[$key],"\n";
    }
}

echo "---------------\n";

while ( list($key,) = each($array) ) {
    echo $array[$key],"\n";
    reset($array);
    while ( list($key,) = each($array) ) {
        echo "\t",$array[$key],"\n";
    }
}
Run Code Online (Sandbox Code Playgroud)

这将输出:

foo
        foo
        bar
        baz
bar
        foo
        bar
        baz
baz
        foo
        bar
        baz
---------------
foo
        foo
        bar
        baz
Run Code Online (Sandbox Code Playgroud)

正如您所看到的,对于大小为3的数组,foreach需要3次迭代,而while只需3次.这就是您的性能问题.

为什么while更快?

因为在第二个(内部)的末尾while,内部指针$array将指向数组的末尾,因此第一个(外部)while将简单地停止.

使用a foreach,因为您正在使用2个不同调用的结果array_keys,所以使用2个不共享相同内部指针的不同数组,因此没有理由循环停止.一个简单return的第二个(内部)foreach应该解决问题.

  • 是的,就读吧!`show_weight`调用`calculate`,它在同一个数组上执行**另一个嵌套循环**.这与我在答案中解释的完全相同.使用`while`所有这些循环共享相同的内部指针并影响彼此.使用`foreach`每个循环是独立的,因此它运行*方式*更多迭代,因此性能问题. (2认同)
  • 现货和优秀答案!对于任何将`while`循环转换为`foreach`循环并认为它是一个完全相同的替代品的人来说,这应该是一个巨大的通知.特别是现在php 7.2已经出来了. (2认同)
  • @rlanvin是分析中的重点.这有点像拥有这个嵌套循环:for($ i = 0; $ i <1000; ++ $ i){for($ i = 0; $ i <1000; ++ $ i){// do stuff}把它变成这个:for($ i = 0; $ i <1000; ++ $ i){for($ j = 0; $ j <1000; ++ $ j){// do stuff}}然后想知道为什么第二个版本需要1000倍的时间.你以前的代码可能通过从两个不同的地方改变内部指针来"有效地"隐藏一些微妙的正确性错误(或者可能没有).这主要是为什么我们弃用每个(),很容易出错 (2认同)