Depuis PHP7, nous pouvons maintenant tiliser un typehint scalaire et demander des types stricts pour chaque fichier . Y a-t-il des avantages en termes de performances à utiliser ces fonctionnalités? Si oui, comment?
Autour des interwebs, je n'ai trouvé que des avantages conceptuels, tels que:
Aujourd'hui, l'utilisation de types scalaires et stricts en PHP7 n'améliore pas les performances.
PHP7 n'a pas de compilateur JIT.
Si à un certain moment dans l'avenir PHP obtient un compilateur JIT, il n'est pas trop difficile d'imaginer des optimisations qui pourraient être effectuées avec les informations de type supplémentaires.
Lorsqu'il s'agit d'optimisations sans JIT, les types scalaires ne sont que partiellement utiles.
Prenons le code suivant:
<?php
function (int $a, int $b) : int {
return $a + $b;
}
?>
Voici le code généré par Zend pour cela:
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
est l'opcode qui effectue la vérification de type et la coercition pour les paramètres reçus. L'opcode suivant est 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();
}
Sans comprendre ce que fait ce code, vous pouvez voir qu'il est plutôt complexe.
Ainsi, la cible omettrait ZEND_RECV
Complètement et remplacer ZEND_ADD
Par ZEND_ADD_INT_INT
Qui n'a pas besoin d'effectuer de vérification (au-delà de la protection) ou de branchement, car les types de paramètres sont connus.
Afin de les omettre et d'avoir un ZEND_ADD_INT_INT
, Vous devez pouvoir déduire de manière fiable les types de $a
Et $b
Au moment de la compilation. L'inférence de compilation est parfois facile, par exemple, $a
Et $b
Sont des entiers littéraux ou des constantes.
Littéralement hier , PHP 7.1 a quelque chose de vraiment similaire: il existe maintenant des gestionnaires de type spécifiques pour certains opcodes haute fréquence comme ZEND_ADD
. Opcache est capable d'inférer le type de certaines variables, il est même capable d'inférer les types de variables dans un tableau dans certains cas et de changer les opcodes générés pour utiliser le ZEND_ADD
normal, pour utiliser un gestionnaire spécifique au type:
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();
}
Encore une fois, sans comprendre ce que cela fait, vous pouvez dire que c'est beaucoup plus simple à exécuter.
Ces optimisations sont très cool, cependant, les optimisations les plus efficaces et les plus intéressantes viendront lorsque PHP a un JIT.
Y a-t-il des avantages en termes de performances à utiliser ces fonctionnalités? Si oui, comment?
Pas encore .
Mais c'est la première étape pour une génération d'opcode plus efficace. Selon RFC: Scalar Type Hints's Future Scope :
Étant donné que les indices de type scalaire garantissent qu'un argument passé sera d'un certain type dans un corps de fonction (au moins initialement), cela pourrait être utilisé dans le moteur Zend pour les optimisations. Par exemple, si une fonction prend deux arguments flottants et fait de l'arithmétique avec eux, les opérateurs arithmétiques n'ont pas besoin de vérifier les types de leurs opérandes.
Dans la version précédente de php, il n'y avait aucun moyen de savoir quel type de paramètre pouvait être transmis à une fonction, ce qui rend vraiment difficile l'approche de compilation JIT pour obtenir des performances supérieures, comme Facebook HHVM faire.
@ircmaxell dans son blog mentionne la possibilité d'amener tout cela au niveau supérieur avec une compilation native, ce qui serait encore mieux que JIT.
Du point de vue des performances, le type d'indices scalaires ouvre les portes de l'implémentation de ces optimisations. Mais n'améliore pas les performances en soi.