¿Por qué require_once es tan malo de usar?

143

Todo lo que leo sobre mejores prácticas de codificación de PHP sigue diciendo que no lo use require_oncedebido 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.

Uberfuzzy
fuente
9
Esta pregunta es bastante antigua ahora, y las respuestas son dudosamente relevantes. Sería genial ver un conjunto actualizado de respuestas de los participantes :)
Purefan

Respuestas:

108

require_oncey include_onceambos requieren que el sistema mantenga un registro de lo que ya se ha incluido / requerido. Cada *_oncellamada 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 *_oncedeclaraciones, 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_oncey es curioso que require_onceaparentemente sea más lento hhvm. Nuevamente, esto solo es relevante para su código si está ejecutando *_oncemiles 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.
Oli
fuente
29
Dudo que su método definido () sea más rápido que la tabla de búsqueda incorporada, pero estoy de acuerdo con su punto general: ¿seguramente no es un problema?
Bobby Jack
1
Estoy bastante seguro de que tienes razón, Bobby, pero no estoy abogando por las definiciones más de una vez. Es solo una opción. El tiempo que llevaría interpretar el código podría incluso hacerlo marginalmente más lento, pero dicho esto, no sé cuán exhaustivo es el método interno. Podría hacer un trabajo extra para garantizar que no haya duplicados.
Oli
8
El otro inconveniente es que APC no almacena en caché las llamadas include_once y require_once IIRC
dcousineau
3
Acabo de hacer una prueba muy básica de los dos métodos: realicé 1,000,000 de iteraciones, incluido un archivo que simplemente definió un 'testinclude' constante a verdadero. En la primera prueba, utilicé require_once, la segunda utilicé if (! Define ('testinclude')) y los resultados fueron interesantes: Requiere: 0.81639003753662 No definido: 0.17906713485718 Definido es 0.63732290267944 microsegundos más rápido.
Travis Weston
El caso con define será más rápido ya que no está realizando ningún tipo de comprobaciones adicionales, como el uso de realpath. No es comparar dos cosas que son realmente iguales.
jgmjgm
150

Este hilo me pone nervioso, porque ya ha habido una "solución publicada", y es, a todos los efectos, incorrecto. Vamos a enumerar:

  1. 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)

  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í.

Edward Z. Yang
fuente
14
Esto tiene 4 años y en su mayor parte ya no se aplica define(), require_once()y defined()todos toman alrededor de 1-2 microsegundos cada uno en mi máquina.
Daniel Beardsley
72
Pero eso es 2 microsegundos antes de que el usuario tenga la página. ¡Más de un año de visitas a la página, eso podría ahorrarle al usuario 3 segundos completos! ¡Podrían ver una décima parte de un comercial en ese momento! Piensa en el usuario. No desperdicies microsegundos.
Andrew Ensley
15
Para que todos estén al tanto del sarcasmo, un microsegundo es 1/1000000 de segundo.
Andy Chase
1
@ AndrewEnsley Simplemente estás equivocado con todo tu sarcasmo. Usted ignora el hecho de que PHP también se ejecuta en microprocesadores, 1 microsegundo en su PC es de varios milisegundos en un microprocesador. ¿Y qué hay de tener 20 archivos de inclusión, un proyecto más grande? Eso es 20 veces el retraso de varios milisegundos introducido, por lo que ya estamos llegando a un punto que es notable para un humano. Si ese script se llama con frecuencia, causará problemas de rendimiento en el sistema. La optimización no es una broma y el mundo entero no está cambiando su PC. Hay diez miles de CPU en uso.
John
2
@Juan. Era una broma hecha de buen humor. Sin malicia prevista. Si vale la pena optimizar sus inclusiones, siga adelante.
Andrew Ensley
66

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 ).

Terson
fuente
66
Y cualquier "optimización" que tenga que ejecutar 10,000 veces para ver una diferencia tan minúscula ni siquiera merece la pena preocuparse. Use un generador de perfiles y descubra dónde están los cuellos de botella reales en su aplicación. Dudo que esta pregunta sea el cuello de botella.
DGM
1
Lo que todo esto significa es que no importa en absoluto. Usa lo que mejor te funcione lógicamente.
Buttle Butkus
wat are jiffies
OverCoder
21

¿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 includey include_oncees que include_onceagrega 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.

nickf
fuente
uno es, chazzuka.com/blog/?p=163 realmente no "no lo hicieron", pero se acumulan demasiadas cosas "caras". y, de hecho, todos los archivos incluidos / requeridos son añadidos a una matriz interna (theres una función para devolverlo), supongo que la de _once tienen que bucle que la matriz y hacer de STRCMP, que suman
Zuirdj
7

Una mejor manera de hacer las cosas es usar un enfoque orientado a objetos y usar __autoload () .

Greg
fuente
3
pero el primer ejemplo en la página de objetos de carga automática que
vinculaste
Yo no compro esto. Hay MUCHAS situaciones en las que OO no encaja de manera tan apropiada como otros paradigmas, por lo que no debe forzarlo a obtener las pequeñas ventajas que pueda haber con __autoload ().
Bobby Jack
1
pensaría que la carga automática en realidad llevaría más tiempo que * _ una vez (suponiendo que solo requiera lo que necesita).
nickf
No, no lo es, al menos no definitivamente, la carga automática aún debe incluirse de alguna manera y es un último recurso para PHP antes de que falle el error, por lo que en realidad PHP realmente realiza comprobaciones potencialmente innecesarias en todos los lugares que se aplicarían para incluir / requerir y DESPUÉS ESO llamaría carga automática (si está definida) ... PD: __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; )
jave.web
6

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 helperfunciones. Para asegurarse de que helpersesté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_once15 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 helpersvale 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 helpersarchivo en una solicitud dada es nula, entonces requiredefinitivamente debería estar en su clase principal. Tener require_onceen cada clase por separado se convierte en un desperdicio de recursos.


La require_oncefunció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.

hexalys
fuente
5

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
Steve Clay
fuente
5

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 .

Annika Backstrom
fuente
Gracias por el puntero al artículo. require_once () es un buen cinturón de seguridad sobre archivos con doble inclusión, y continuaremos usándolo, pero poder limpiarlo es bueno.
Andy Lester
2

Incluso si require_oncey include_once son más lentos que requirey 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_oncepermite 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 .

NeuroXc
fuente
0

Usted prueba, usando include, la alternativa de oli y __autoload (); y probarlo con algo como APC instalado.

Dudo que usar constante acelere las cosas.

Dinoboff
fuente
0

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.

Lucas Oman
fuente
-2

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.

Ekkmanz
fuente
Algunas referencias estarían en orden.
Peter Mortensen
-3

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.

ashchristopher
fuente
1
No necesariamente. En realidad, puede conectar un controlador de errores o un controlador de apagado para darle al usuario una página de error agradable (aunque la gente rara vez lo hace). Como desarrollador, preferiría que las cosas se equivoquen de inmediato.
Edward Z. Yang
1
O, según sea el caso, no fallar con gracia: si no se requiere algún archivo vital () correctamente, es una buena idea simplemente rendirse y detenerse. Pero eso es require vs include, mientras que creo que la pregunta se centra más en require vs require_once.
HoboBen
-4

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.

Joe Scylla
fuente