PHP7中的标量和严格类型是否是性能增强功能?

39
自从PHP7之后,我们现在可以使用标量类型提示并在每个文件中请求严格类型。使用这些特性是否有性能优势?如果有,是什么?
在网络上,我只发现了概念上的好处,例如:
- 更精确的错误 - 避免不必要的类型强制转换问题 - 更语义化的代码,避免在使用他人代码时产生误解 - 更好的IDE对代码的评估

5
标量类型提示的一个潜在性能增强效果是类型转换被迫提前发生,这可能会减少后续的类型转换次数。 - NikiC
2个回答

45

今天,在PHP7中使用标量和严格类型并不能提高性能。

PHP7没有JIT编译器。

如果在将来的某个时候,PHP会配备JIT编译器,那么很容易想象通过额外的类型信息可以执行优化。

在没有JIT的情况下进行优化时,标量类型仅有部分帮助。

让我们看一下以下代码:

<?php
function (int $a, int $b) : int {
    return $a + $b;
}
?>

这是Zend为此生成的代码:

function name: {closure}
L2-4 {closure}() /usr/src/scalar.php - 0x7fd6b30ef100 + 7 ops
 L2    #0     RECV                    1                                         $a                  
 L2    #1     RECV                    2                                         $b                  
 L3    #2     ADD                     $a                   $b                   ~0                  
 L3    #3     VERIFY_RETURN_TYPE      ~0                                                            
 L3    #4     RETURN                  ~0                                                            
 L4    #5     VERIFY_RETURN_TYPE                                                                    
 L4    #6     RETURN                  null

ZEND_RECV是执行接收的参数的类型验证和强制转换的操作码。下一个操作码是ZEND_ADD

ZEND_VM_HANDLER(1, ZEND_ADD, CONST|TMPVAR|CV, CONST|TMPVAR|CV)
{
    USE_OPLINE
    zend_free_op free_op1, free_op2;
    zval *op1, *op2, *result;

    op1 = GET_OP1_ZVAL_PTR_UNDEF(BP_VAR_R);
    op2 = GET_OP2_ZVAL_PTR_UNDEF(BP_VAR_R);
    if (EXPECTED(Z_TYPE_INFO_P(op1) == IS_LONG)) {
        if (EXPECTED(Z_TYPE_INFO_P(op2) == IS_LONG)) {
            result = EX_VAR(opline->result.var);
            fast_long_add_function(result, op1, op2);
            ZEND_VM_NEXT_OPCODE();
        } else if (EXPECTED(Z_TYPE_INFO_P(op2) == IS_DOUBLE)) {
            result = EX_VAR(opline->result.var);
            ZVAL_DOUBLE(result, ((double)Z_LVAL_P(op1)) + Z_DVAL_P(op2));
            ZEND_VM_NEXT_OPCODE();
        }
    } else if (EXPECTED(Z_TYPE_INFO_P(op1) == IS_DOUBLE)) {
        if (EXPECTED(Z_TYPE_INFO_P(op2) == IS_DOUBLE)) {
            result = EX_VAR(opline->result.var);
            ZVAL_DOUBLE(result, Z_DVAL_P(op1) + Z_DVAL_P(op2));
            ZEND_VM_NEXT_OPCODE();
        } else if (EXPECTED(Z_TYPE_INFO_P(op2) == IS_LONG)) {
            result = EX_VAR(opline->result.var);
            ZVAL_DOUBLE(result, Z_DVAL_P(op1) + ((double)Z_LVAL_P(op2)));
            ZEND_VM_NEXT_OPCODE();
        }
    }

    SAVE_OPLINE();
    if (OP1_TYPE == IS_CV && UNEXPECTED(Z_TYPE_INFO_P(op1) == IS_UNDEF)) {
        op1 = GET_OP1_UNDEF_CV(op1, BP_VAR_R);
    }
    if (OP2_TYPE == IS_CV && UNEXPECTED(Z_TYPE_INFO_P(op2) == IS_UNDEF)) {
        op2 = GET_OP2_UNDEF_CV(op2, BP_VAR_R);
    }
    add_function(EX_VAR(opline->result.var), op1, op2);
    FREE_OP1();
    FREE_OP2();
    ZEND_VM_NEXT_OPCODE_CHECK_EXCEPTION();
}

即使不理解这些代码的作用,你也可以看到它相当复杂。

因此,目标是完全省略 ZEND_RECV ,并将 ZEND_ADD 替换为 ZEND_ADD_INT_INT ,后者无需执行任何检查(除了保护)或分支,因为参数的类型已知。

为了省略它们,并获得一个 ZEND_ADD_INT_INT ,你需要能够在编译时可靠地推断出 $a$b 的类型。编译时推断有时很容易,例如,$a$b 是文字整数或常量。

字面上讲,就像昨天一样,PHP 7.1 得到了类似的东西:现在对于一些高频操作码,例如ZEND_ADD,有了类型特定的处理程序。Opcache 能够推断出某些变量的类型,在某些情况下甚至可以推断出数组内变量的类型,并且改变生成的操作码以使用正常的ZEND_ADD,使用类型特定的处理程序:

ZEND_VM_TYPE_SPEC_HANDLER(ZEND_ADD, (res_info == MAY_BE_LONG && op1_info == MAY_BE_LONG && op2_info == MAY_BE_LONG), ZEND_ADD_LONG_NO_OVERFLOW, CONST|TMPVARCV, CONST|TMPVARCV, SPEC(NO_CONST_CONST,COMMUTATIVE))
{
    USE_OPLINE
    zval *op1, *op2, *result;

    op1 = GET_OP1_ZVAL_PTR_UNDEF(BP_VAR_R);
    op2 = GET_OP2_ZVAL_PTR_UNDEF(BP_VAR_R);
    result = EX_VAR(opline->result.var);
    ZVAL_LONG(result, Z_LVAL_P(op1) + Z_LVAL_P(op2));
    ZEND_VM_NEXT_OPCODE();
}

不需要理解上述内容的含义,你就能发现这个执行过程要简单得多 (much)

这些优化方式非常酷,然而最有效、最有趣的优化是在PHP拥有JIT时出现的。


5
确切地说,它甚至会降低性能,但只是微不足道的程度;-) 减少了几个检查,但并没有什么明显的变化。 - bwoebi
3
从技术上讲,这是正确的。但增强或降低不应该成为你使用(或不使用)此类功能的决定因素。 - Joe Watkins
1
@JoeWatkins 在 PHP 8.1 中是否仍然是这种情况? - Erich

17
这些特性有性能上的优助吗?如果有,是怎么样的呢?
目前还没有。
但这是更高效操作码生成的第一步。根据“标量类型提示”的RFC文档 Future Scope
“因为标量类型提示保证了函数体内传递的参数会是某种特定的类型(至少最初是这样),这在Zend引擎中可以用作优化。例如,如果一个函数有两个浮点类型提示参数,并对它们进行算术运算,则算术运算符不需要检查其操作数的类型。”
以前版本的php无法知道哪种类型的参数可以传递到函数中,这使得实现类似Facebook的HHVM所采用的JIT编译方法来获得卓越的性能变得非常困难。

@ircmaxell在他的博客中提到了使用本地编译将所有内容提升到更高水平的可能性,这比JIT还要好。

从性能的角度来看,类型标量提示为实现这些优化打开了大门。但本身并不能提高性能。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接