Todo lo que leo sobre mejores prácticas de codificación de PHP sigue diciendo que no lo use require_once
debido a la velocidad.
¿Por qué es esto?
¿Cuál es la forma adecuada / mejor de hacer lo mismo require_once
? Si importa, estoy usando PHP 5.
Todo lo que leo sobre mejores prácticas de codificación de PHP sigue diciendo que no lo use require_once
debido a la velocidad.
¿Por qué es esto?
¿Cuál es la forma adecuada / mejor de hacer lo mismo require_once
? Si importa, estoy usando PHP 5.
Respuestas:
require_once
y include_once
ambos requieren que el sistema mantenga un registro de lo que ya se ha incluido / requerido. Cada *_once
llamada significa verificar ese registro. Así que no hay duda alguna un trabajo extra que se realiza allí, pero lo suficiente para detrimento de la velocidad de toda la aplicación?
... Realmente lo dudo ... No, a menos que estés en un hardware realmente viejo o lo estés haciendo mucho .
Si está haciendo miles de *_once
, podría hacer el trabajo usted mismo de una manera más ligera. Para aplicaciones simples, basta con asegurarse de que solo lo haya incluido una vez , pero si todavía obtiene redefinir errores, podría hacer algo como esto:
if (!defined('MyIncludeName')) {
require('MyIncludeName');
define('MyIncludeName', 1);
}
Personalmente me quedaré con las *_once
declaraciones, pero en el tonto punto de referencia de millones de pases, puedes ver una diferencia entre los dos:
php hhvm
if defined 0.18587779998779 0.046600103378296
require_once 1.2219581604004 3.2908599376678
10-100 × más lento require_once
y es curioso que require_once
aparentemente sea más lento hhvm
. Nuevamente, esto solo es relevante para su código si está ejecutando *_once
miles de veces.
<?php // test.php
$LIMIT = 1000000;
$start = microtime(true);
for ($i=0; $i<$LIMIT; $i++)
if (!defined('include.php')) {
require('include.php');
define('include.php', 1);
}
$mid = microtime(true);
for ($i=0; $i<$LIMIT; $i++)
require_once('include.php');
$end = microtime(true);
printf("if defined\t%s\nrequire_once\t%s\n", $mid-$start, $end-$mid);
<?php // include.php
// do nothing.
Este hilo me pone nervioso, porque ya ha habido una "solución publicada", y es, a todos los efectos, incorrecto. Vamos a enumerar:
Las definiciones son realmente caras en PHP. Puede buscarlo o probarlo usted mismo, pero la única forma eficiente de definir una constante global en PHP es a través de una extensión. (Las constantes de clase son en realidad un rendimiento bastante decente, pero este es un punto discutible, debido a 2)
Si está utilizando require_once()
adecuadamente, es decir, para la inclusión de clases, ni siquiera necesita una definición; solo verifica si class_exists('Classname')
. Si el archivo que incluye contiene código, es decir, lo está utilizando de manera procesal, no hay absolutamente ninguna razón que require_once()
sea necesaria para usted; cada vez que incluye el archivo presume que está haciendo una llamada de subrutina.
Entonces, por un tiempo, muchas personas usaron el class_exists()
método para sus inclusiones. No me gusta porque es fugitivo, pero tenían buenas razones para hacerlo: require_once()
era bastante ineficiente antes de algunas de las versiones más recientes de PHP. Pero eso se ha solucionado, y creo que el bytecode adicional que tendría que compilar para el condicional, y la llamada al método adicional, superarían con creces cualquier comprobación interna de tabla hash.
Ahora, una admisión: es difícil probar estas cosas, porque representa muy poco tiempo de ejecución.
Aquí está la pregunta en la que debería estar pensando: incluye, como regla general, son caros en PHP, porque cada vez que el intérprete toca uno, debe volver al modo de análisis, generar los códigos de operación y luego retroceder. Si tiene más de 100 incluye, esto definitivamente tendrá un impacto en el rendimiento. La razón por la que usar o no usar require_once es una pregunta tan importante es porque dificulta la vida de los cachés de código de operación. Aquí se puede encontrar una explicación de esto , pero todo se reduce a eso:
Si durante el tiempo de análisis, usted sabe exactamente qué archivos de inclusión necesitará durante toda la vida de la solicitud, require()
los que están al principio y el caché de código de operación se encargará de todo lo demás por usted.
Si no está ejecutando un caché de código de operación, está en un lugar difícil. Incluir todos los elementos incluidos en un archivo (no haga esto durante el desarrollo, solo en la producción) ciertamente puede ayudar a analizar el tiempo, pero es difícil de hacer, y además, necesita saber exactamente qué incluirá durante el solicitud.
La carga automática es muy conveniente, pero lenta, por la razón de que la lógica de carga automática debe ejecutarse cada vez que se realiza una inclusión. En la práctica, descubrí que cargar automáticamente varios archivos especializados para una solicitud no causa demasiado problema, pero no debe cargar automáticamente todos los archivos que necesitará.
Si tiene tal vez 10 incluye (esto es una parte muy atrás del cálculo de la envolvente), todas estas patadas no valen la pena: simplemente optimice las consultas de su base de datos o algo así.
define()
, require_once()
y defined()
todos toman alrededor de 1-2 microsegundos cada uno en mi máquina.
Sentí curiosidad y revisé el enlace de Adam Backstrom a Tech Your Universe . Este artículo describe una de las razones por las que debe usarse require en lugar de require_once. Sin embargo, sus afirmaciones no fueron válidas para mi análisis. Me interesaría ver dónde podría haber analizado mal la solución. Usé PHP 5.2.0 para las comparaciones.
Comencé creando 100 archivos de encabezado que usaba require_once para incluir otro archivo de encabezado. Cada uno de estos archivos se parecía a:
<?php
// /home/fbarnes/phpperf/hdr0.php
require_once "../phpperf/common_hdr.php";
?>
Los creé usando un truco rápido de Bash:
for i in /home/fbarnes/phpperf/hdr{00..99}.php; do
echo "<?php
// $i" > $i
cat helper.php >> $i;
done
De esta forma, podría cambiar fácilmente entre require_once y require al incluir los archivos de encabezado. Luego creé un app.php para cargar los cien archivos. Esto se parecía a:
<?php
// Load all of the php hdrs that were created previously
for($i=0; $i < 100; $i++)
{
require_once "/home/fbarnes/phpperf/hdr$i.php";
}
// Read the /proc file system to get some simple stats
$pid = getmypid();
$fp = fopen("/proc/$pid/stat", "r");
$line = fread($fp, 2048);
$array = split(" ", $line);
// Write out the statistics; on RedHat 4.5 with kernel 2.6.9
// 14 is user jiffies; 15 is system jiffies
$cntr = 0;
foreach($array as $elem)
{
$cntr++;
echo "stat[$cntr]: $elem\n";
}
fclose($fp);
?>
Comparé los encabezados require_once con los encabezados require que usaban un archivo de encabezado similar al siguiente:
<?php
// /home/fbarnes/phpperf/h/hdr0.php
if(!defined('CommonHdr'))
{
require "../phpperf/common_hdr.php";
define('CommonHdr', 1);
}
?>
No encontré mucha diferencia al ejecutar esto con require vs. require_once. De hecho, mis pruebas iniciales parecían implicar que require_once fue un poco más rápido, pero no necesariamente lo creo. Repetí el experimento con 10000 archivos de entrada. Aquí vi una diferencia constante. Ejecuté la prueba varias veces, los resultados son cercanos, pero el uso de require_once utiliza en promedio 30.8 jiffies de usuario y 72.6 jiffies de sistema; el uso requiere usos en promedio 39.4 jiffies de usuario y 72.0 jiffies del sistema. Por lo tanto, parece que la carga es ligeramente menor usando require_once. Sin embargo, el tiempo del reloj de pared aumenta ligeramente. Las 10,000 llamadas require_once usan 10.15 segundos en promedio y las 10,000 llamadas require usan 9.84 segundos en promedio.
El siguiente paso es analizar estas diferencias. Utilicé strace para analizar las llamadas al sistema que se están realizando.
Antes de abrir un archivo desde require_once se realizan las siguientes llamadas al sistema:
time(NULL) = 1223772434
lstat64("/home", {st_mode=S_IFDIR|0755, st_size=4096, ...}) = 0
lstat64("/home/fbarnes", {st_mode=S_IFDIR|0755, st_size=4096, ...}) = 0
lstat64("/home/fbarnes/phpperf", {st_mode=S_IFDIR|0755, st_size=4096, ...}) = 0
lstat64("/home/fbarnes/phpperf/h", {st_mode=S_IFDIR|0755, st_size=270336, ...}) = 0
lstat64("/home/fbarnes/phpperf/h/hdr0.php", {st_mode=S_IFREG|0644, st_size=88, ...}) = 0
time(NULL) = 1223772434
open("/home/fbarnes/phpperf/h/hdr0.php", O_RDONLY) = 3
Esto contrasta con requiere:
time(NULL) = 1223772905
lstat64("/home", {st_mode=S_IFDIR|0755, st_size=4096, ...}) = 0
lstat64("/home/fbarnes", {st_mode=S_IFDIR|0755, st_size=4096, ...}) = 0
lstat64("/home/fbarnes/phpperf", {st_mode=S_IFDIR|0755, st_size=4096, ...}) = 0
lstat64("/home/fbarnes/phpperf/h", {st_mode=S_IFDIR|0755, st_size=270336, ...}) = 0
lstat64("/home/fbarnes/phpperf/h/hdr0.php", {st_mode=S_IFREG|0644, st_size=146, ...}) = 0
time(NULL) = 1223772905
open("/home/fbarnes/phpperf/h/hdr0.php", O_RDONLY) = 3
Tech Your Universe implica que require_once debería hacer más llamadas a lstat64. Sin embargo, ambos realizan la misma cantidad de llamadas lstat64. Posiblemente, la diferencia es que no estoy ejecutando APC para optimizar el código anterior. Sin embargo, a continuación comparé la salida de strace para todas las ejecuciones:
[fbarnes@myhost phpperf]$ wc -l strace_1000r.out strace_1000ro.out
190709 strace_1000r.out
210707 strace_1000ro.out
401416 total
Efectivamente, hay aproximadamente dos llamadas más al sistema por archivo de encabezado cuando se utiliza require_once. Una diferencia es que require_once tiene una llamada adicional a la función time ():
[fbarnes@myhost phpperf]$ grep -c time strace_1000r.out strace_1000ro.out
strace_1000r.out:20009
strace_1000ro.out:30008
La otra llamada al sistema es getcwd ():
[fbarnes@myhost phpperf]$ grep -c getcwd strace_1000r.out strace_1000ro.out
strace_1000r.out:5
strace_1000ro.out:10004
Esto se llama porque decidí la ruta relativa referenciada en los archivos hdrXXX. Si hago de esto una referencia absoluta, entonces la única diferencia es la llamada de tiempo adicional (NULL) realizada en el código:
[fbarnes@myhost phpperf]$ wc -l strace_1000r.out strace_1000ro.out
190705 strace_1000r.out
200705 strace_1000ro.out
391410 total
[fbarnes@myhost phpperf]$ grep -c time strace_1000r.out strace_1000ro.out
strace_1000r.out:20008
strace_1000ro.out:30008
Esto parece implicar que podría reducir la cantidad de llamadas al sistema utilizando rutas absolutas en lugar de rutas relativas. La única diferencia fuera de eso son las llamadas de tiempo (NULL) que parecen usarse para instrumentar el código para comparar lo que es más rápido.
Otra nota es que el paquete de optimización de APC tiene una opción llamada "apc.include_once_override" que afirma que reduce la cantidad de llamadas al sistema realizadas por require_once e include_once (consulte la documentación de PHP ).
¿Puede darnos algún enlace a estas prácticas de codificación que diga evitarlo? En lo que a mí respecta, es un completo problema . No he mirado el código fuente yo mismo, pero me imagino que la única diferencia entre include
y include_once
es que include_once
agrega ese nombre de archivo a una matriz y verifica la matriz cada vez. Sería fácil mantener esa matriz ordenada, por lo que buscar en ella debería ser O (log n), e incluso una aplicación de tamaño medio solo tendría un par de docenas incluidas.
Una mejor manera de hacer las cosas es usar un enfoque orientado a objetos y usar __autoload () .
__autoload()
se desaconseja y puede quedar en desuso en el futuro, debería usar spl_autoload_register(...)
estos días ... PS2: no me malinterpreten, a veces uso la funcionalidad de carga automática; )
No está usando la función que es mala. Es una comprensión incorrecta de cómo y cuándo usarlo, en una base de código general. Solo agregaré un poco más de contexto a esa noción posiblemente incomprendida:
La gente no debería pensar que require_once es una función lenta. Tienes que incluir tu código de una forma u otra. require_once()
vs.require()
la velocidad no es el problema. Se trata del rendimiento que obstaculiza las advertencias que pueden resultar por usarlo a ciegas. Si se usa ampliamente sin tener en cuenta el contexto, puede generar una gran pérdida de memoria o un código inútil.
Lo que he visto que es realmente malo, es cuando usan enormes marcos monolíticos require_once()
de todas las maneras incorrectas, especialmente en un entorno complejo orientado a objetos (OO).
Tome el ejemplo del uso require_once()
en la parte superior de cada clase como se ve en muchas bibliotecas:
require_once("includes/usergroups.php");
require_once("includes/permissions.php");
require_once("includes/revisions.php");
class User{
// User functions
}
Entonces el User
clase está diseñada para usar las otras tres clases. ¡Lo suficientemente justo!
Pero ahora, ¿qué pasa si un visitante está navegando por el sitio y ni siquiera ha iniciado sesión y se carga el marco? require_once("includes/user.php");
por cada solicitud individual?
Incluye 1 + 3 clases innecesarias que nunca usará durante esa solicitud en particular. Así es como los frameworks hinchados terminan usando 40 MB por solicitud en lugar de 5 MB o menos.
¡La otra forma en que se puede usar mal es cuando una clase es reutilizada por muchos otros! Digamos que tiene alrededor de 50 clases que usan helper
funciones. Para asegurarse de que helpers
estén disponibles para esas clases cuando se cargan, obtiene:
require_once("includes/helpers.php");
class MyClass{
// Helper::functions(); // etc..
}
No hay nada malo aquí per se. Sin embargo, si una solicitud de página incluye 15 clases similares. Estás corriendo require_once
15 veces, o para una buena visual:
require_once("includes/helpers.php");
require_once("includes/helpers.php");
require_once("includes/helpers.php");
require_once("includes/helpers.php");
require_once("includes/helpers.php");
require_once("includes/helpers.php");
require_once("includes/helpers.php");
require_once("includes/helpers.php");
require_once("includes/helpers.php");
require_once("includes/helpers.php");
require_once("includes/helpers.php");
require_once("includes/helpers.php");
require_once("includes/helpers.php");
require_once("includes/helpers.php");
require_once("includes/helpers.php");
El uso de require_once () afecta técnicamente el rendimiento para ejecutar esa función 14 veces, además de tener que analizar esas líneas innecesarias. Con solo otras 10 clases muy utilizadas con ese problema similar, podría dar cuenta de más de 100 líneas de dicho código repetitivo sin sentido.
Con eso, probablemente valga la pena usarlo require("includes/helpers.php");
en el arranque de su aplicación o marco. Pero como todo es relativo, todo depende de si helpers
vale la pena ahorrar entre 15 y 100 líneas de la frecuencia de peso versus uso de la clase require_once()
. Pero si la probabilidad de no usar el helpers
archivo en una solicitud dada es nula, entonces require
definitivamente debería estar en su clase principal. Tener require_once
en cada clase por separado se convierte en un desperdicio de recursos.
La require_once
función es útil cuando es necesario, pero no debe considerarse como una solución monolítica para usar en todas partes para cargar todas las clases.
El wiki de PEAR2 (cuando existía) solía enumerar buenas razones para abandonar todas las directivas require / include a favor de la carga automática , al menos para el código de la biblioteca. Estos lo atan a estructuras de directorio rígidas cuando modelos de empaque alternativos como phar están en el horizonte.
Actualización: como la versión archivada de la wiki es fea, he copiado las razones más convincentes a continuación:
- Se requiere include_path para usar un paquete (PEAR). Esto hace que sea difícil agrupar un paquete PEAR dentro de otra aplicación con su propio include_path, crear un solo archivo que contenga las clases necesarias, mover un paquete PEAR a un archivo phar sin una extensa modificación del código fuente.
- cuando require_once de nivel superior se mezcla con require_once condicional, esto puede generar un código que no se puede almacenar en caché mediante cachés de código de operación como APC, que se incluirá con PHP 6.
- relativo require_once requiere que include_path ya esté configurado con el valor correcto, lo que hace imposible usar un paquete sin el include_path adecuado
Las *_once()
funciones stat cada directorio principal para garantizar que el archivo que está incluyendo no sea el mismo que el que ya se ha incluido. Esa es parte de la razón de la desaceleración.
Recomiendo usar una herramienta como Siege para la evaluación comparativa. Puede probar todas las metodologías sugeridas y comparar los tiempos de respuesta.
Más información require_once()
está en Tech Your Universe .
Incluso si require_once
y include_once
son más lentos que require
y include
(o cualquier alternativa que pueda existir), estamos hablando del nivel más pequeño de micro-optimización aquí. Dedica mucho más tiempo a optimizar esa consulta de bucle o base de datos mal escrita que preocuparse por algo así require_once
.
Ahora, uno podría argumentar que require_once
permite prácticas de codificación deficientes porque no necesita prestar atención para mantener sus inclusiones limpias y organizadas, pero eso no tiene nada que ver con la función en sí misma y especialmente con su velocidad.
Obviamente, la carga automática es mejor por la limpieza del código y la facilidad de mantenimiento, pero quiero dejar en claro que esto no tiene nada que ver con la velocidad .
Usted prueba, usando include, la alternativa de oli y __autoload (); y probarlo con algo como APC instalado.
Dudo que usar constante acelere las cosas.
Sí, es un poco más caro de lo que se requiere (). Creo que el punto es que si puede mantener su código lo suficientemente organizado como para no duplicar las inclusiones, no use las funciones * _once (), ya que le ahorrará algunos ciclos.
Pero usar las funciones _once () no va a matar su aplicación. Básicamente, simplemente no lo use como una excusa para no tener que organizar sus inclusiones . En algunos casos, su uso sigue siendo inevitable, y no es gran cosa.
Creo que en la documentación de PEAR, hay una recomendación para require, require_once, include e include_once. Sí sigo esa directriz. Su aplicación sería más clara.
No tiene nada que ver con la velocidad. Se trata de fallar con gracia.
Si require_once () falla, su script está listo. Nada más se procesa. Si usa include_once (), el resto de su secuencia de comandos intentará continuar renderizando, por lo que sus usuarios podrían no ser más sabios de algo que ha fallado en su secuencia de comandos.
Mi opinión personal es que el uso de require_once (o include_once) es una mala práctica porque require_once lo comprueba si ya incluyó ese archivo y suprime los errores de archivos doblemente incluidos que resultan en errores fatales (como la declaración duplicada de funciones / clases / etc.) .
Debe saber si necesita incluir un archivo.