otro - ¿Por qué son las llamadas a función PHP*tan*costosas?
llamar funcion php desde onclick (5)
¿La sobrecarga para llamar a una función de usuario es realmente tan grande? ¿O es realmente tan grande ahora? Tanto el PHP como el hardware de la computadora han avanzado a pasos agigantados en los casi 7 años desde que se hizo originalmente esta pregunta.
He escrito mi propia secuencia de comandos de evaluación comparativa que llama a mt_rand () en un bucle tanto directamente como a través de una llamada de función de usuario:
const LOOPS = 10000000;
function myFunc ($a, $b)
{
return mt_rand ($a, $b);
}
// Call mt_rand, simply to ensure that any costs for setting it up on first call are already accounted for
mt_rand (0, 1000000);
$start = microtime (true);
for ($x = LOOPS; $x > 0; $x--)
{
mt_rand (0, 1000000);
}
echo "Inline calling mt_rand() took " . (microtime(true) - $start) . " second(s)/n";
$start = microtime (true);
for ($x = LOOPS; $x > 0; $x--)
{
myFunc (0, 1000000);
}
echo "Calling a user function took " . (microtime(true) - $start) . " second(s)/n";
Los resultados en PHP 7 en una computadora de escritorio vintage i5 2016 (más específicamente, CPU Intel® Core ™ i5-6500 a 3.20GHz × 4) son los siguientes:
La llamada en línea mt_rand () tomó 3.5181620121002 segundo (s) Llamar a una función de usuario tomó 7.2354700565338 segundo (s)
La sobrecarga de llamar a una función de usuario parece duplicar aproximadamente el tiempo de ejecución. Pero tomó 10 millones de iteraciones para que se volviera particularmente notable. Esto significa que, en la mayoría de los casos, las diferencias entre el código en línea y una función del usuario probablemente sean insignificantes. Solo debería preocuparse por ese tipo de optimización en los circuitos más internos de su programa, e incluso solo si el benchmarking demuestra un claro problema de rendimiento allí. Cualquier otra cosa sería una micro-optimisation que rendiría poco o ningún beneficio de rendimiento significativo para una mayor complejidad en el código fuente.
Si su script PHP es lento, es casi seguro que se reducirá a E / S o una mala elección del algoritmo en lugar de una sobrecarga de llamada a la función. Conectarse a una base de datos, hacer una solicitud de CURL, escribir en un archivo o simplemente hacer eco en stdout son todos órdenes de magnitud más costosos que llamar a una función de usuario. Si no me cree, haga que mt_rand y myfunc hagan eco de sus resultados y vea cuánto más lento se ejecuta el script.
En la mayoría de los casos, la mejor manera de optimizar un script PHP es minimizar la cantidad de E / S que tiene que hacer (solo seleccionar lo que necesita en consultas DB en lugar de confiar en PHP para filtrar las filas no deseadas, por ejemplo) u obtener es almacenar en caché operaciones de E / S a través de algo como Memcache para reducir el costo de E / S en archivos, bases de datos, sitios remotos, etc.
Una llamada de función en PHP es costosa. Aquí hay un pequeño punto de referencia para probarlo:
// create test string
$string = str_repeat(''a'', 1000);
$maxChars = 500;
// with function call
$start = microtime(true);
for ($i = 0; $i < RUNS; ++$i) {
strlen($string) <= $maxChars;
}
echo ''with function call: '', microtime(true) - $start, "/n";
// without function call
$start = microtime(true);
for ($i = 0; $i < RUNS; ++$i) {
!isset($string[$maxChars]);
}
echo ''without function call: '', microtime(true) - $start;
Esto prueba un código funcionalmente idéntico usando primero una función ( strlen
) y luego sin usar una función ( isset
no es una función).
Obtengo el siguiente resultado:
with function call: 4.5108239650726
without function call: 0.84017300605774
Como puede ver, la implementación que utiliza una llamada de función es más de cinco (5.38) veces más lenta que la implementación que no llama a ninguna función.
Me gustaría saber por qué una llamada a función es tan costosa. ¿Cuál es el principal cuello de botella? ¿Es la búsqueda en la tabla hash? ¿O qué es tan lento?
Volví a consultar esta pregunta y decidí volver a ejecutar el punto de referencia con XDebug completamente deshabilitado (no solo con el perfil deshabilitado). Esto demostró que mis pruebas fueron bastante intrincadas, esta vez, con 10000000 carreras que obtuve:
with function call: 3.152988910675
without function call: 1.4107749462128
Aquí, una llamada de función solo es aproximadamente dos veces (2.23) lenta, por lo que la diferencia es mucho menor.
Acabo de probar el código anterior en una instantánea de PHP 5.4.0 y obtuve los siguientes resultados:
with function call: 2.3795559406281
without function call: 0.90840601921082
Aquí la diferencia se hizo un poco más grande de nuevo (2.62). (Pero en general, el tiempo de ejecución de ambos métodos disminuyó bastante).
Creo que la respuesta de Rich Remer es bastante precisa. Estás comparando manzanas con naranjas con tu ejemplo original. Prueba este en su lugar:
<?php
$RUNS = 100000;
// with function call
$x = "";
$start = microtime(true);
for ($i = 0; $i < $RUNS; ++$i) {
$x = $i.nothing($x);
}
echo ''with function call: '', microtime(true) - $start, "/n<br/>";
// without function call
$x = "";
$start = microtime(true);
for ($i = 0; $i < $RUNS; ++$i) {
$x = $i.$x;
}
echo ''without function call: '', microtime(true) - $start;
function nothing($x) {
return $x;
}
La única diferencia en este ejemplo es la llamada a la función en sí misma. Con 100.000 ejecuciones (como se indicó anteriormente) vemos una diferencia de <1% en el uso de la llamada de función de nuestro resultado:
with function call: 2.4601600170135
without function call: 2.4477159976959
Por supuesto, todo esto depende de lo que hace su función y lo que considera costoso . Si nothing()
devolviera $x*2
(y reemplazamos la llamada no funcional de $x = $i.$x
con $x = $i.($x*2)
veríamos ~ 4% de pérdida al usar la llamada a la función.
Las llamadas a funciones son caras en PHP porque se están haciendo muchas cosas.
Tenga en cuenta que isset
no es una función (tiene un código de operación especial), por lo que es más rápido.
Para un programa simple como este:
<?php
func("arg1", "arg2");
Hay seis (cuatro + uno para cada argumento) códigos de operación:
1 INIT_FCALL_BY_NAME ''func'', ''func'' 2 EXT_FCALL_BEGIN 3 SEND_VAL ''arg1'' 4 SEND_VAL ''arg2'' 5 DO_FCALL_BY_NAME 2 6 EXT_FCALL_END
Puede verificar las implementaciones de los zend_vm_def.h
de zend_vm_def.h
en zend_vm_def.h
. ZEND_
en los nombres, por ejemplo, para ZEND_INIT_FCALL_BY_NAME
y busque.
ZEND_DO_FCALL_BY_NAME
es particularmente complicado. Luego está la implementación de la función en sí misma, que debe desenrollar la pila, verificar los tipos, convertir los zvals y posiblemente separarlos y al trabajo real ...
Las llamadas a funciones son costosas por el motivo perfectamente explicado por @Artefacto anterior. Tenga en cuenta que su rendimiento está directamente relacionado con la cantidad de parámetros / argumentos involucrados. Esta es un área a la que le he prestado mucha atención al desarrollar mi propio marco de aplicaciones. Cuando tiene sentido y es posible evitar una llamada a función, lo hago.
Un ejemplo de esto es un reemplazo reciente de is_numeric()
e is_integer()
con una prueba booleana simple en mi código, especialmente cuando se pueden hacer varias llamadas a estas funciones. Mientras que algunos pueden pensar que tales optimizaciones no tienen sentido, noté una mejora dramática en la capacidad de respuesta de mis sitios web a través de este tipo de trabajo de optimización.
La siguiente prueba rápida será VERDADERO para un número y FALSO para cualquier otra cosa.
if ($x == ''0''.$x) { ... }
Mucho más rápido que is_numeric()
e is_integer()
. De nuevo, solo cuando tiene sentido, es perfectamente válido usar algunas optimizaciones.
Yo diría que no lo son. En realidad, no estás probando una llamada a función. Está probando la diferencia entre un cheque de bajo nivel fuera de límites (isset) y caminando a través de una cadena para contar el número de bytes (strlen).
No puedo encontrar ninguna información específica de PHP, pero strlen generalmente se implementa de forma parecida a (incluyendo la sobrecarga de llamada a la función):
$sp += 128;
$str->address = 345;
$i = 0;
while ($str[$i] != 0) {
$i++;
}
return $i < $length;
Una comprobación fuera de límites normalmente se implementaría de la siguiente manera:
return $str->length < $length;
El primero es bucle. El segundo es una prueba simple.