I tipi scalari e rigorosi in PHP7 sono una funzionalità che migliora le performance?

Da PHP 7 ora possiamo usare scaler typehint e chiedere tipi rigorosi per each file . Ci sono delle performance vantaggiose dall'utilizzo di queste funzionalità? Se sì, come?

Intorno all'interwebs ho trovato solo benefici concettuali, come:

  • errori più precisi
  • evitando problemi con la coercizione di tipo indesiderata
  • più codice semantico, evitando equivoci quando si utilizza il codice di altri
  • migliore valutazione IDE del codice

Oggi l'uso di tipi scalari e rigorosi in PHP7 non migliora le performance.

PHP7 non ha un compilatore JIT.

Se a un certo momento in futuro PHP otterrà un compilatore JIT, non è troppo difficile immaginare le ottimizzazioni che potrebbero essere eseguite con le informazioni di tipo aggiuntive.

Quando si tratta di ottimizzazioni senza JIT, i tipi scalari sono solo parzialmente utili.

Prendiamo il seguente codice:

<?php function (int $a, int $b) : int { return $a + $b; } ?> 

Questo è il codice generato da Zend per quello:

 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 è l'opcode che esegue la verifica del tipo e la coercizione per i parametri ricevuti. Il prossimo codice ZEND_ADD è 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(); } 

Senza capire cosa fa qualsiasi di quel codice, puoi vedere che è piuttosto complesso.

Pertanto, l'objective ometterebbe completamente ZEND_RECV e sostituirà ZEND_ADD con ZEND_ADD_INT_INT che non ha bisogno di eseguire alcun controllo (oltre la protezione) o ramificazione, poiché i tipi di parametri sono noti.

Per ometterli e avere un ZEND_ADD_INT_INT devi essere in grado di dedurre attendibilmente i tipi di $a e $b in fase di compilazione. L'inferenza della compilazione del tempo è a volte facile, ad esempio, $a e $b sono interi o costanti letterali.

Letteralmente ieri , PHP 7.1 ha ottenuto qualcosa di molto simile: ora ci sono gestori di tipi specifici per alcuni ZEND_ADD ad alta frequenza come ZEND_ADD . Opcache è in grado di inferire il tipo di alcune variables, in alcuni casi è anche in grado di inferire i tipi di variables all'interno di un arrays e di modificare i ZEND_ADD generati per utilizzare il normale ZEND_ADD , per utilizzare un gestore specifico del tipo:

 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(); } 

Di nuovo, senza capire che cosa fa, si può dire che questo è molto più semplice da eseguire.

Queste ottimizzazioni sono molto interessanti, tuttavia, le ottimizzazioni più efficaci e più interessanti verranno quando PHP avrà un JIT.

Ci sono delle performance vantaggiose dall'utilizzo di queste funzionalità? Se sì, come?

Non ancora

Ma questo è il primo passo per una generazione di opcode più efficiente. Secondo RFC: Future Scope del tipo scalare :

Poiché gli hint del tipo scalare garantiscono che un argomento passato sarà di un certo tipo all'interno di un corpo di una function (alless inizialmente), questo potrebbe essere utilizzato nel motore Zend per le ottimizzazioni. Ad esempio, se una function accetta due argomenti con virgola mobile e fa aritmetica con essi, non è necessario che gli operatori aritmetici controllino i tipi dei loro operandi.

Nella versione precedente di php non c'era modo di sapere quale tipo di parametro poteva essere passato ad una function, il che rende davvero difficile avere un approccio di compilazione JIT per get performance superiori, come l' HHVM di Facebook.

@ircmaxell nel suo blog menziona la possibilità di portre tutto questo al livello successivo con la compilazione nativa, che sarebbe anche meglio di JIT.

Dal punto di vista delle performance, digitare suggerimenti scalari apre le porte per l'implementazione di tali ottimizzazioni. Ma non migliora le performance in sé e per sé.