一般的PHP地雷

时间:2009-02-04 16:28:17

标签: php performance

其他人在编写PHP Web应用程序时发现了什么惊喜?编译时类继承是众所周知的问题,但我知道其他几个问题,并希望尝试建立一个语言的顶级问题列表。

注意:

我作为一名高级PHP5开发人员担任过多个职位,因此PHP工作支付了我的账单,这个问题并不意味着将PHP作为一种语言,因为我使用的每种语言都有一些众所周知的或者不是这样的众所周知的惊喜。

24 个答案:

答案 0 :(得分:19)

我不确定这是否重要,但是编译PHP脚本的需求是一个巨大的性能问题。在任何严肃的PHP项目中,您需要某种编译器缓存,如APCeAcceleratorPHP Accelerator或(商业)Zend Platform

答案 1 :(得分:15)

Recursive references leak memory

如果您创建两个对象并将它们存储在彼此的属性中,则垃圾收集器将永远不会触及它们:

$a = new stdClass;
$b = new stdClass;
$a->b = $b;
$b->a = $a;

当一个大类创建一个通常存储主类的小助手对象时,这实际上很容易做到:

// GC will never clean up any instance of Big.
class Big {
  function __construct() {
    $this->helper = new LittleHelper($this);
  }
}
class LittleHelper {
  function __construct(Big $big) {
    $this->big = $big;
  }
}

只要PHP针对短快速页面请求,他们就不太可能解决此问题。这意味着PHP不能依赖于守护进程或其他具有较长生命周期的应用程序。

答案 2 :(得分:13)

require_once和include_once在过度使用时通常会导致主要的性能杀手。如果你包含/要求一个包含类的文件...这样的模式可以节省一些严重的处理时间。

class_exists("myFoo") or require("myFoo.someClass.php");

更新:    这仍然是一个问题 - http://www.techyouruniverse.com/software/php-performance-tip-require-versus-require_once

更新:   阅读以下问题的所选答案:   Would performance suffer using autoload in php and searching for the class file?    如果按照这些方式实施,您几乎可以尽可能地减少文件包含/要求的惩罚。

答案 3 :(得分:11)

一个有趣的地雷:Global variables can affect $_SESSION when register_globals is on。但我想这就是当地雷矿本身register_globals开启时会发生什么。

答案 4 :(得分:10)

NULL和“0”字符串在Php

中是纯粹的邪恶
if ("0" == false) //true
if ("0" == NULL)  //true
if ("0" == "NULL")//true

答案 5 :(得分:7)

  • foreach()默默地在后台复制数组并通过该副本进行迭代。如果您有一个大型阵列,这将降低性能。在这些情况下,foreach()的by-reference选项是php5的新功能或使用for()循环。

  • 注意平等(==)与身份(===)。

  • 请注意构成empty()与构成isset()的构成。


现在我有更多的时间了更多的地雷:

  • 不要比较浮点数是否相等。 PHP不是matlab,它不是专为精确浮点运算而设计的。试试这个:
if (0.1 + 0.2 == 0.3)
  echo "equal";
else
  echo "nope"; // <-- ding ding
  • 同样,不要忘记你的八卦!带有前导零的int将被转换为八进制。
if (0111 == 111)
  echo "equal";
else
  echo "nope"; // <-- ding ding

答案 6 :(得分:6)

事后有点明显,但是在foreach中使用时,一个众所周知的问题与范围和引用有关。

foreach($myArray as &$element){
   //do something to the element here... maybe trim or something more complicated
}
//Multiple lines or immediately after the loop

$element = $foobar;

数组中的最后一个单元格现在变为$ foobar,因为上面的foreach中的引用仍然在当前上下文范围内。

答案 7 :(得分:5)

应始终避免使用@错误消音器。

一个例子:

// Don't let the user see an error if this unimportant header file is missing:
@include 'header.inc.php';

使用上面的代码,您永远不会知道header.inc.php中的任何代码或来自header.inc.php的任何函数中的任何错误,如果某处有致命错误,网页将停止,无法找出错误是什么。

答案 8 :(得分:5)

不知道operator precedence会导致一些问题:

if ($foo = getSomeValue() && $bar) {
    // …
}
// equals
if ($foo = (getSomeValue() && $bar)) {
    // …
}

答案 9 :(得分:5)

__autoload()最近被证明是我的主要地雷。我们的一些遗留代码和库使用class_exists(),并尝试自动加载从未打算以这种方式加载的类。许多致命的错误和警告。如果您有自动加载,仍然可以使用class_exists(),但第二个参数(自PHP 5.2.0起新)必须设置为false

答案 10 :(得分:4)

我看到人们陷入困境的大问题是精确度(用PHP和其他语言)。

如果你想要一点乐趣,可以将任何浮点数与一个整数进行比较&gt; =并找出你获得预期结果的次数。

这是许多在PHP内部工作的人的垮台,并试图根据不允许四舍五入的比较做出逻辑决策。

例如 - fabric

面料以1码或1码的单位出售,并保持织物剩余的精确测量库存。

如果这个系统没有用整数表示,而是以浮点表示,那么就很难做出坚实的判决。

你最好的选择是将1码半表示为1,例如,如果你有300码的面料,你的库存量为600(600码半单位)。

无论如何,这就是我的问题 - 由于不了解精确度而需要重构4个月的编程....

答案 11 :(得分:4)

数字字符串自动转换为整数

到目前为止,这是PHP中最丑陋,最晦涩的黑客攻击。每当你有一个全部数字的字符串时,它会自动被视为在某些情况下它是整数。

php > var_dump("0" == "00");
bool(true)

这可能会与PHP的“关联数组”相结合,导致$a == $b并不意味着$arr[$a] == $arr[$b];

的奇怪之处
php > var_dump(array('00'=>'str(zerozero)', '0'=>'str(zero)'));
array(2) {
  ["00"]=>
  string(13) "str(zerozero)"
  [0]=>
  string(9) "str(zero)"
}

答案 12 :(得分:3)

我最喜欢的PHP问题:

考虑这一点包括:

# ... lots of code ...
$i = 42;
# ... more code ...

然后在某个地方使用这个:

for($i = 0; $i < 10; $i++){
    # ...
    include 'that_other_file.php';
}

然后尝试猜测循环运行的次数。是的,曾经。词汇 范围(和适当的动态范围)都是解决的问题。但 不是PHP。

答案 13 :(得分:3)

如果您习惯使用智能逻辑运算符的语言,您将尝试执行以下操作:

$iShouldTalkTo = $thisObj || $thatObj;

在PHP中,$iShouldTalkTo现在是一个布尔值。你被迫写:

$iShouldTalkTo = $thisObj ? $thisObj : $thatObj;

在PHP的早期设计决策如何试图掌握无能的程序员以换取蹒跚学步的程序员的所有例子中,这可能是最让我恼火的人。

switch()构造中的深部脑损伤比比皆是。考虑一下:

switch($someVal) {
case true  :
    doSomething();
    break;
case 20    :
    doSomethingElse();
    break;
}

事实证明doSomethingElse()永远不会被调用,因为'case true'将吸收所有 $ someVal的真实案例。

认为这是合理的,也许吧?好吧,试试这个:

for($ix = 0; $ix < 10; $ix++) {
    switch($ix) {
    case 3  :
        continue;
    default :
        echo ':';
    }
    echo $ix;
}

猜猜它的输出是什么?应该是:0:1:2:4:5:6:7:8:9,对吗?不,是:0:1:23:4:5:6:7:8:9。也就是说,忽略continue语句的语义,并将其视为break

答案 14 :(得分:3)

最糟糕的一个是 PHP的“关联数组”的概念,它完全是数组,字典和列表的混合。 PHP的作者似乎不确定它在每种情况下应该如何表现,这会导致奇怪,例如我们不同的数组加运算符和array_merge函数的行为。

php > $a = array(1=>'one');
php > $b = array(2=>'two');
php > var_dump($a+$b); /* plus preserves original keys */
array(2) {
  [1]=>
  string(3) "one"
  [2]=>
  string(3) "two"
}
php > var_dump(array_merge($a,$b)); /* array_merge reindexes numeric keys */
array(2) {
  [0]=>
  string(3) "one"
  [1]=>
  string(3) "two"
}


php > $a = array(1=>'one');
php > $b = array(1=>'another one');
php > var_dump($a+$b);  /* plus ignores duplicate keys, keeping the first value */
array(1) {
  [1]=>
  string(3) "one"
}
php > var_dump(array_merge($a,$b)); /* array_merge just adds them all, reindexing */
array(2) {
  [0]=>
  string(3) "one"
  [1]=>
  string(11) "another one"
}

php > $a = array(1,2,3);
php > $b = array(4,5,6);
/* non-associative arrays are really associative arrays with numeric keys… */
php > var_dump($a+$b);  /* … so plus doesn’t work as you’d normally expect */
array(3) {
  [0]=>
  int(1)
  [1]=>
  int(2)
  [2]=>
  int(3)
}
php > var_dump(array_merge($a,$b));  /* you should use array_merge instead */
array(6) {
  [0]=>
  int(1)
  [1]=>
  int(2)
  [2]=>
  int(3)
  [3]=>
  int(4)
  [4]=>
  int(5)
  [5]=>
  int(6)
}

答案 15 :(得分:2)

PHP中的另一个陷阱,我看到来自其他语言但不常见的人的错误。

<?php
/**
 * regular
 */
echo (true && true); // 1
echo (true && false); // nothing

echo (true || false); // 1
echo (false || false); // nothing

echo (true xor false); // 1
echo (false xor false); // nothing

/**
 * bitwise
 */
echo (true & true); // 1
echo (true & false); // 0

echo (true | false); // 1
echo (false | false); // 0

echo (true ^ false); // 1
echo (false ^ false); // 0
?>

答案 16 :(得分:2)

PHP应用程序的性能问题通常是以下之一:

  • 文件系统访问 - 读取和写入磁盘
    • 这是APC,eAccelerator等派上用场的地方,它们通过在内存中缓存已解析的PHP文件来减少文件系统访问
  • 数据库 - 慢查询,大数据集
  • 网络I / O - 访问外部资源

使用PHP(或使用任何语言编写的任何Web应用程序)遇到性能问题的情况非常罕见。上述问题通常比代码执行慢几个数量级。

与往常一样,分析您的代码!

答案 17 :(得分:2)

运行PHP时的总内存。许多大型项目只包含所有类文件,并在需要时使用它们。这增加了每次运行时PHP需要使用的总内存。

使用Frames或IFrames的项目也可以轻松地将内存使用量增加一倍。

因此,对类文件进行条件加载,不加载任何未使用的文件

答案 18 :(得分:2)

没有为if / else分支获取编译器消息:

if( $foo )
{
  some_function();
}
else
{
  non_existing_function();   // oops!
}

在您输入non_existing_function为假的情况之前,PHP不会提及$foo不存在。


忘记设置:

error_reporting( E_ALL );

因此没有抓住通知,花时间调试:

  • 不存在的变量
  • 无效的对象属性
  • 无效的数组键

将不同“类型”/来源的字符串粘贴在一起,而不会转义它们:

// missing mysql_real_escape_string() or an int cast !
$sql = "SELECT * FROM persons WHERE id=$id";

// missing htmlentities() and urlencode() !
$html = "<a href='?page=$id'>$text</a>";  

答案 19 :(得分:2)

根据Why is calling a function (such as strlen, count etc) on a referenced value so slow?

如果你通过引用将一个变量传递给一个函数,然后在它上面调用一个函数,它就会难以置信地慢。

如果循环遍历函数调用并且变量很大,它可能比通过值传递变量要慢许多个数量级。

示例:

<?php
function TestCount(&$aArray)
{
    $aArray = range(0, 100000);
    $fStartTime = microtime(true);

    for ($iIter = 0; $iIter < 1000; $iIter++)
    {
        $iCount = count($aArray);
    }

    $fTaken = microtime(true) - $fStartTime;

    print "took $fTaken seconds\n";
}

$aArray = array();
TestCount($aArray);
?>

这在我的机器上运行大约需要20秒(在PHP 5.3上)。

但是,如果我将函数更改为按值传递(即function TestCount($aArray)而不是function TestCount(&$aArray)),则它会在大约2毫秒内运行 - 真正快10,000倍

任何按值传递的函数都是如此 - 内置函数(如strlen)和用户定义函数都是如此。

这是一个我以前没有意识到的相当可怕的停机坪!

幸运的是,有一个简单的解决方法适用于许多情况 - 在循环中使用临时局部变量,并在最后复制到引用变量。

答案 20 :(得分:1)

$x = array();
$x == null ? "true": "false";

输出为“true”。

$x = array("foo");
$x == null ? "true": "false";

输出为“false”;

答案 21 :(得分:1)

类型转换和三重相等

通常在大多数语言中,当您对两种不同类型的数据进行操作时,您会获得异常,或者其中一种被转换为更常规的数据。在语言中,除了PHP之外,字符串被认为比整数更通用。只有PHP才能:

php > var_dump('nada' == 0);
bool(true)

为了应对PHP引入的三重等价运算符。如果值具有相同类型和相同值,则定义返回true。适用于上面的例子:

php > var_dump('nada' === 0);
bool(false)

但是当你喜欢值相等时,它也表现得非常难看。

php > var_dump(0.0 === 0);
bool(false)

如果您使用任何其他语言的经验来使用PHP,那么您一定会遇到问题。

答案 22 :(得分:1)

在一开始,人们可能会花费大量时间来调试这种代码:

$a = 1;
echo $a;      # 1
echo "$a";    # 1
echo '$a';    # $a
该死的报价!非常令人沮丧:(

答案 23 :(得分:1)

只是想到了一个惊喜。将回调应用于数组的array_map是一个严重的性能杀手。我不完全确定原因,但我认为这与PHP的循环写入机制复制有关。