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

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

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

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

person igorsantos07    schedule 05.10.2015    source источник
comment
Один потенциально повышающий производительность эффект подсказок скалярного типа заключается в том, что приведение типа принудительно происходит раньше, что может уменьшить количество последующих приведений.   -  person NikiC    schedule 07.10.2015


Ответы (2)


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

person Joe Watkins    schedule 05.10.2015
comment
Если быть точным, это даже снижает производительность на очень небольшую… скажем, незначительную величину ;-) Это на несколько проверок меньше, но ничего особо заметного. - person bwoebi; 05.10.2015
comment
Это технически верно. Но улучшение или деградация не должны быть решающим фактором при использовании (или не использовании) такой функции. - person Joe Watkins; 05.10.2015

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

пока нет.

Но это первый шаг к более эффективной генерации кода операции. Согласно RFC: Scalar Type Hints Future Scope:

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

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

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

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

person Federkun    schedule 05.10.2015