Являются ли скалярные и строгие типы в PHP7 функцией повышения производительности?

Начиная с PHP7 мы можем теперь используйте скалярную типизацию и запрашивайте строгие типы для каждого файла. Есть ли какие-либо преимущества в производительности от использования этих функций? Если да, то как?

Вокруг межсетей я нашел только концептуальные преимущества, такие как:

  • более точные ошибки
  • избегать проблем с принуждением нежелательного типа
  • больше семантического кода, избегая недоразумений при использовании чужого кода
  • лучшая оценка IDE кода

32

Решение

Сегодня использование скалярных и строгих типов в 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();
}

Опять же, не понимая, что из этого делает, вы можете сказать, что это много проще выполнить.

Эти оптимизации очень крутые, но наиболее эффективные, и наиболее интересные оптимизации придут, когда в PHP есть JIT.

39

Другие решения

Есть ли какие-либо преимущества в производительности от использования этих функций? Если да, то как?

Не еще.

Но это первый шаг для более эффективной генерации кода операции. В соответствии с RFC: скалярный тип подсказок Будущая сфера:

Поскольку подсказки скалярного типа гарантируют, что переданный аргумент будет
определенный тип в теле функции (по крайней мере, на начальном этапе), это может
использовать в Zend Engine для оптимизации. Например, если
Функция принимает два аргумента с плавающей запятой и выполняет арифметику с
им нет необходимости в арифметических операторах для проверки типов
их операндов.

В предыдущей версии php не было никакого способа узнать, какой тип параметра можно передать функции, что затрудняет использование подхода JIT-компиляции для достижения превосходной производительности, как в Facebook HHVM делать.

@ircmaxell в его блог упоминает о возможности перенести все это на следующий уровень с нативной компиляцией, которая была бы даже лучше, чем JIT.

С точки зрения производительности, тип скалярных подсказок открывает двери для реализации этих оптимизаций. Но не увеличивает производительность сама по себе.

17

По вопросам рекламы [email protected]