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

Posted

技术标签:

【中文标题】PHP7 中的标量和严格类型是性能增强功能吗?【英文标题】:Are scalar and strict types in PHP7 a performance enhancing feature? 【发布时间】:2016-01-01 14:41:15 【问题描述】:

php7 开始,我们现在可以use scalar typehint and ask for strict types on a per-file basis。使用这些功能是否有任何性能优势?如果是,怎么做?

在互联网上,我只发现了概念上的好处,例如:

更精确的错误 避免不必要的类型强制问题 更多语义代码,避免使用他人代码时的误解 更好的 IDE 代码评估

【问题讨论】:

标量类型提示的一个潜在性能增强效果是强制类型转换提前发生,这可能减少后续转换的数量。 【参考方案1】:

如今,在 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 是文字整数或常量。

字面意思是yesterday,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();

同样,在不了解其中任何一个的情况下,您可以说这更容易执行。

这些优化非常酷,然而,当 PHP 有 JIT 时,最有效、最有趣的优化就会出现。

【讨论】:

准确地说,它甚至会降低性能非常小……说可以忽略不计;-) 少了一些检查,但没有什么特别明显的。 这在技术上是正确的。但是增强或降级不应成为您是否使用(或不使用)此类功能的决定因素。【参考方案2】:

使用这些功能是否有任何性能优势?如果是,怎么做?

还没有

但这是更有效地生成操作码的第一步。根据 RFC: Scalar Type Hints's Future Scope:

因为标量类型提示保证传递的参数将是 函数体内的某种类型(至少在最初),这可以 在 Zend 引擎中用于优化。例如,如果一个 函数接受两个浮点型参数并进行算术运算 它们,算术运算符不需要检查类型 他们的操作数。

在以前的php版本中,无法知道可以向函数传递什么样的参数,这使得像facebook的HHVM那样使用JIT编译方法来获得卓越的性能真的很困难。

@ircmaxell 在他的blog 中提到了通过原生编译将所有这些提升到一个新水平的可能性,这甚至比 JIT 更好。

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

【讨论】:

以上是关于PHP7 中的标量和严格类型是性能增强功能吗?的主要内容,如果未能解决你的问题,请参考以下文章

PHP7的新功能

PHP7新特性

标量类型与返回值类型声明

PHP7新功能及语法变化总结

浅析PHP7新功能及语法变化总结

PHP7:标量返回类型声明不应该接受整数吗?