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

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

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

  • более точные ошибки
  • избегая проблем с нежелательным типом принуждения
  • более семантический код, избегая недоразумений при использовании кода другого пользователя
  • лучшая оценка кода 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 – это код операции, который выполняет проверку типа и принуждение для полученных параметров. Следующий код операции – 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 операций, таких как 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.

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

Еще нет .

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

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

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

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

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