web-dev-qa-db-fra.com

Les types scalaires et stricts de PHP7 améliorent-ils les performances?

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:

  • erreurs plus précises
  • éviter les problèmes de coercition de type indésirable
  • code plus sémantique, évitant les malentendus lors de l'utilisation du code d'un autre
  • mieux IDE évaluation du code
35
igorsantos07

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.

42
Joe Watkins

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.

17
Federkun