PHP7 中的标量和严格类型是性能增强功能吗?
Are scalar and strict types in PHP7 a performance enhancing feature?
从 PHP7 开始,我们现在可以 use scalar typehint and ask for strict types on a per-file basis。使用这些功能是否有任何性能优势?如果是,如何?
关于互联网,我只发现了概念上的好处,例如:
- 更精确的错误
- 避免不需要的类型强制问题
- 更多语义代码,避免在使用他人代码时产生误解
- 更好IDE代码评价
今天,在 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
is the opcode that performs type verification and coercion for the received parameters. The next opcode is 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 时出现。
Are there any performance benefits from using these features? If yes, how?
还没有。
但这是更高效的操作码生成的第一步。根据 RFC:标量类型提示 Future Scope:
Because scalar type hints guarantee that a passed argument will be of
a certain type within a function body (at least initially), this could
be used in the Zend Engine for optimisations. For example, if a
function takes two float-hinted arguments and does arithmetic with
them, there is no need for the arithmetic operators to check the types
of their operands.
在之前的版本php中,没有办法知道什么样的参数可以传递给一个函数,这使得像facebook的[=13这样的JIT编译方法很难获得更好的性能。 =]做。
@ircmaxell 在他的 blog 中提到了通过本机编译将所有这些提升到一个新水平的可能性,这甚至比 JIT 更好。
从性能的角度来看,类型标量提示为实现这些优化打开了大门。但本身并不能提高性能。
从 PHP7 开始,我们现在可以 use scalar typehint and ask for strict types on a per-file basis。使用这些功能是否有任何性能优势?如果是,如何?
关于互联网,我只发现了概念上的好处,例如:
- 更精确的错误
- 避免不需要的类型强制问题
- 更多语义代码,避免在使用他人代码时产生误解
- 更好IDE代码评价
今天,在 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
is the opcode that performs type verification and coercion for the received parameters. The next opcode is 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 时出现。
Are there any performance benefits from using these features? If yes, how?
还没有。
但这是更高效的操作码生成的第一步。根据 RFC:标量类型提示 Future Scope:
Because scalar type hints guarantee that a passed argument will be of a certain type within a function body (at least initially), this could be used in the Zend Engine for optimisations. For example, if a function takes two float-hinted arguments and does arithmetic with them, there is no need for the arithmetic operators to check the types of their operands.
在之前的版本php中,没有办法知道什么样的参数可以传递给一个函数,这使得像facebook的[=13这样的JIT编译方法很难获得更好的性能。 =]做。
@ircmaxell 在他的 blog 中提到了通过本机编译将所有这些提升到一个新水平的可能性,这甚至比 JIT 更好。
从性能的角度来看,类型标量提示为实现这些优化打开了大门。但本身并不能提高性能。