Подтвердить что ты не робот

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

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

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

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

Ответ 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 являются целыми буквами или константами.

Буквально вчера, 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: Scope Scalear Type Hints:

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

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

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

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