web-dev-qa-db-fra.com

Pourquoi require_once est-il si mauvais à utiliser?

Tout ce que je lis sur mieux PHP pratiques de codage PHP) ne cesse de dire de ne pas utiliser require_once à cause de la vitesse.

Pourquoi est-ce?

Quelle est la meilleure façon/meilleure de faire la même chose que require_once? Si cela compte, j'utilise PHP5.

140
Uberfuzzy

require_once et include_once _ les deux exigent que le système garde un journal de ce qui a déjà été inclus/requis. Chaque *_once appel signifie vérifier ce journal. Donc, il y a certainement n pe du travail supplémentaire qui y est effectué, mais suffisant pour nuire à la vitesse de l'application entière?

... J'en doute vraiment ... sauf si vous utilisez vraiment vieux matériel ou si vous le faites a beaucoup.

Si vous êtes faites des milliers de *_once, vous pouvez faire le travail vous-même de façon plus légère. Pour les applications simples, il suffit de s’assurer de ne l’avoir inclus qu’une fois devrait mais si vous obtenez toujours des erreurs de redéfinition, vous pouvez obtenir quelque chose comme ceci:

if (!defined('MyIncludeName')) {
    require('MyIncludeName');
    define('MyIncludeName', 1);
}

Je vais personnellement rester avec le *_once _ déclarations mais sur repère stupide million-passes, vous pouvez voir une différence entre les deux:

                php                  hhvm
if defined      0.18587779998779     0.046600103378296
require_once    1.2219581604004      3.2908599376678

10-100 × plus lent avec require_once et c'est curieux que require_once est apparemment plus lent dans hhvm. Encore une fois, cela ne concerne votre code que si vous utilisez *_once des milliers de fois.


<?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.
104
Oli

Ce fil me fait grincer des dents, car il y a déjà eu une "solution publiée", et c'est, à toutes fins pratiques, faux. Énumérons:

  1. Les définitions sont vraiment coûteuses en PHP. Vous pouvez le rechercher ou le tester vous-même, mais le seul moyen efficace de définir une constante globale dans PHP est via une extension. (Les constantes de classe sont en réalité assez convenables Performance sage, mais c'est un point discutable, à cause de 2)

  2. Si vous utilisez require_once() correctement, c'est-à-dire pour l'inclusion de classes, vous n'avez même pas besoin de définir; il suffit de vérifier si class_exists('Classname'). Si le fichier que vous incluez contient du code, c'est-à-dire que vous l'utilisez de manière procédurale, il n'y a absolument aucune raison pour que require_once() soit nécessaire pour vous; chaque fois que vous incluez le fichier que vous supposez faire un appel de sous-routine.

Ainsi, pendant un certain temps, beaucoup de gens ont utilisé la méthode class_exists() pour leurs inclusions. Je n'aime pas ça parce que c'est moche, mais ils avaient de bonnes raisons de: require_once() était plutôt inefficace avant certaines des versions les plus récentes de PHP. Mais cela a été corrigé, et j’affirme que le bytecode supplémentaire que vous auriez à compiler pour l’appel conditionnel et l’appel de méthode supplémentaire l'emporterait de loin sur les vérifications de hachage internes.

Maintenant, un aveu: ce genre de choses est difficile à tester, car il représente si peu de temps d’exécution.

Voici la question à laquelle vous devriez penser: include, en règle générale, coûte cher en PHP, car chaque fois que l'interpréteur touche l'une d'elles, il doit repasser en mode analyse, générer les opcodes, puis revenir en arrière. Si vous avez plus de 100 inclus, cela aura certainement un impact sur les performances. La raison pour laquelle l’utilisation ou non de require_once est une question aussi importante est qu’elle rend la vie difficile aux caches opcode. Un explication pour cela peut être trouvé ici, mais cela revient à dire que:

  • Si, au cours de l'analyse, vous savez exactement de quels fichiers include vous aurez besoin pendant toute la durée de la requête, require() ceux du début et le cache d'opcode se chargera de tout le reste.

  • Si vous n'exécutez pas de cache opcode, vous êtes dans une situation difficile. Inliner tous vos inclus dans un seul fichier (ne le faites pas pendant le développement, mais uniquement en production) peut certainement aider à analyser le temps, mais c'est une tâche difficile, et vous devez aussi savoir exactement ce que vous allez inclure pendant le processus. demande.

  • Le chargement automatique est très pratique, mais lent, car la logique de chargement automatique doit être exécutée chaque fois qu'une inclusion est effectuée. En pratique, j'ai constaté que le chargement automatique de plusieurs fichiers spécialisés pour une demande ne posait pas trop de problèmes, mais que vous ne devriez pas charger automatiquement tous les fichiers dont vous aurez besoin.

  • Si vous avez peut-être 10 inclusions (c'est un très retour du calcul de l'enveloppe), toute cette branlette n'en vaut pas la peine: optimisez simplement vos requêtes de base de données ou quelque chose du genre.

147
Edward Z. Yang

Je suis devenu curieux et j'ai vérifié le lien d'Adam Backstrom vers Tech Your Universe . Cet article décrit l'une des raisons pour lesquelles require devrait être utilisé à la place de require_once. Cependant, leurs revendications ne résistent pas à mon analyse. Je serais intéressé de voir où j'ai peut-être mal analysé la solution. J'ai utilisé PHP 5.2.0 pour les comparaisons.

J'ai commencé par créer 100 fichiers d'en-tête qui utilisaient require_once pour inclure un autre fichier d'en-tête. Chacun de ces fichiers ressemblait à quelque chose comme:

<?php
// /home/fbarnes/phpperf/hdr0.php
require_once "../phpperf/common_hdr.php";

?>

J'ai créé ces derniers en utilisant un hack bash rapide:

for i in /home/fbarnes/phpperf/hdr{00..99}.php; do
  echo "<?php
// $i" > $i
  cat helper.php >> $i;
done

De cette façon, je pourrais facilement permuter entre require_once et require en incluant les fichiers d'en-tête. J'ai ensuite créé un fichier app.php pour charger les cent fichiers. Cela ressemblait à:

<?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 w/ 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);

?>

J'ai mis en contraste les en-têtes require_once avec des en-têtes require utilisant un fichier d'en-tête ressemblant à ceci:

<?php
// /home/fbarnes/phpperf/h/hdr0.php
if(!defined('CommonHdr'))
{
  require "../phpperf/common_hdr.php";
  define('CommonHdr', 1);
}

?>

Je n'ai pas trouvé beaucoup de différence en exécutant ceci avec require vs require_once. En fait, mes tests initiaux semblaient indiquer que require_once était légèrement plus rapide, mais je ne le crois pas nécessairement. J'ai répété l'expérience avec 10000 fichiers d'entrée. Ici, j'ai vu une différence constante. J'ai exécuté le test plusieurs fois, les résultats sont proches mais l'utilisation de require_once utilise en moyenne 30,8 jiffies utilisateur et 72,6 jiffies système; using require utilise en moyenne 39,4 jiffies utilisateur et 72,0 jiffies système. Par conséquent, il semble que la charge soit légèrement inférieure avec require_once. Cependant, l'horloge murale est légèrement augmentée. Les 10 000 appels require_once durent en moyenne 10,15 secondes, tandis que 10 000 appels requièrent en moyenne 9,84 secondes.

La prochaine étape consiste à examiner ces différences. J'ai utilisé strace pour analyser les appels système en cours.

Avant d'ouvrir un fichier à partir de require_once, les appels système suivants sont effectués:

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

Cela contraste avec exigent:

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 implique que require_once devrait effectuer plus d'appels lstat64. Cependant, ils effectuent tous les deux le même nombre d'appels lstat64. La différence est peut-être que je n’utilise pas APC pour optimiser le code ci-dessus. Cependant, j’ai ensuite comparé le résultat de strace pour l’ensemble des essais:

[fbarnes@myhost phpperf]$ wc -l strace_1000r.out strace_1000ro.out 
  190709 strace_1000r.out
  210707 strace_1000ro.out
  401416 total

Effectivement, il y a environ deux autres appels système par fichier d'en-tête lors de l'utilisation de require_once. Une différence est que require_once a un appel supplémentaire à la fonction time ():

[fbarnes@myhost phpperf]$ grep -c time strace_1000r.out strace_1000ro.out 
strace_1000r.out:20009
strace_1000ro.out:30008

L'autre appel système est getcwd ():

[fbarnes@myhost phpperf]$ grep -c getcwd strace_1000r.out strace_1000ro.out 
strace_1000r.out:5
strace_1000ro.out:10004

Cela s'appelle parce que j'ai choisi le chemin relatif référencé dans les fichiers hdrXXX. Si je fais de cette référence une référence absolue, alors la seule différence est l'appel de temps supplémentaire (NULL) effectué dans le code:

[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

Cela semble impliquer que vous pouvez réduire le nombre d'appels système en utilisant des chemins absolus plutôt que des chemins relatifs. La seule différence en dehors de cela est le temps (NULL) qui semble être utilisé pour instrumenter le code afin de comparer ce qui est plus rapide.

Une autre remarque est que le package d'optimisation APC a une option appelée "apc.include_once_override" qui prétend réduire le nombre d'appels système effectués par les appels require_once et include_once (voir documents PHP ).

Désolé pour le long post. Je suis curieux.

64
terson

Pouvez-vous nous donner des liens vers ces pratiques de codage qui disent de l'éviter? En ce qui me concerne, c'est un non-problème complet. Je n'ai pas regardé le code source moi-même, mais j'imagine que la seule différence entre include et include_once est-ce include_once ajoute ce nom de fichier à un tableau et vérifie chaque fois le tableau. Il serait facile de garder ce tableau trié. Par conséquent, la recherche devrait être O (log n), et même une application moyennement large n'aurait qu'une douzaine d'inclusions.

20
nickf

Une meilleure façon de faire consiste à utiliser une approche orientée objet et à utiliser __ autoload () .

6
Greg

Le wiki PEAR2 (quand il existait) listait bonnes raisons pour abandonner toutes les directives require/include en faveur de l'autoload au moins pour le code de la bibliothèque. Celles-ci vous lient à des structures de répertoires rigides lorsque d'autres modèles de conditionnement tels que phar se profilent à l'horizon.

Mise à jour: comme la version archivée sur le Web du wiki est extrêmement laide, j'ai copié les raisons les plus convaincantes ci-dessous:

  • include_path est requis pour utiliser un package (PEAR). Il est donc difficile de grouper un package PEAR dans une autre application avec son propre chemin include_path, afin de créer un fichier contenant les classes nécessaires, afin de déplacer un package PEAR une archive phar sans modification extensive du code source.
  • lorsque require_once de niveau supérieur est mélangé à require_once conditionnel, le code qui ne peut pas être caché par les caches d'opcode tels que APC peut être associé à PHP 6.
  • require_once relative requiert que include_path soit déjà défini sur la valeur correcte, ce qui rend impossible l'utilisation d'un package sans include_path approprié
5
Steve Clay

Les fonctions *_once() déclarent chaque répertoire parent afin de garantir que le fichier que vous incluez ne soit pas le même que celui déjà inclus. Cela fait partie de la raison du ralentissement.

Je recommande d'utiliser un outil tel que Siege pour l'analyse comparative. Vous pouvez essayer toutes les méthodologies suggérées et comparer les temps de réponse.

En savoir plus sur require_once() à l'adresse Tech Your Universe .

5
Annika Backstrom

Ce n'est pas en utilisant la fonction qui est mauvaise. C'est une compréhension incorrecte de comment et quand l'utiliser, dans une base de code globale. Je vais juste ajouter un peu plus de contexte à cette notion peut-être mal comprise:

Les gens ne devraient pas penser que require_once est une fonction lente. Vous devez inclure votre code d'une manière ou d'une autre. La vitesse de require_once() vs require() n'est pas le problème. Il s’agit des mises en garde empêchant la performance de l’utiliser aveuglément. Utilisé largement sans tenir compte du contexte, il peut entraîner une perte de mémoire importante ou un code inutile.

Ce que j’ai vu c’est vraiment grave, c’est lorsque d’énormes cadres monolithiques utilisent require_once() de manière erronée, en particulier dans un environnement complexe orienté objet.

Prenons l'exemple de l'utilisation de require_once() en haut de chaque classe, comme dans de nombreuses bibliothèques:

require_once("includes/usergroups.php");
require_once("includes/permissions.php");
require_once("includes/revisions.php");
class User{
  //user functions
}

La classe User est donc conçue pour utiliser les 3 autres classes. C'est suffisant! Mais maintenant, que se passe-t-il si un visiteur navigue sur le site et ne se connecte même pas et que le framework se charge: require_once("includes/user.php"); pour chaque requête.

Cela inclut les classes 1 + 3 inutiles qu'il n'utilisera jamais lors de cette demande particulière. C’est ainsi que les frameworks saturés finissent par utiliser 40 Mo par requête, au lieu de 5 Mo ou moins.


Les autres façons de l'utiliser à mauvais escient, c'est quand une classe est réutilisée par beaucoup d'autres! Disons que vous avez environ 50 classes qui utilisent les fonctions helper. Pour vous assurer que helpers sont disponibles pour ces classes lorsqu'elles sont chargées, vous obtenez:

require_once("includes/helpers.php");
class MyClass{
  //Helper::functions();//etc..
}

Il n'y a rien de mal ici en soi. Cependant, si une requête de page contient 15 classes similaires. Vous exécutez require_once 15 fois, ou pour un joli visuel:

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");

L’utilisation de require_once () affecte techniquement les performances pour exécuter cette fonction 14 fois, en plus de l’analyse de ces lignes inutiles. Avec seulement 10 autres classes très utilisées avec ce problème similaire, il pourrait représenter plus de 100 lignes de ce code répétitif plutôt inutile.

Avec cela, il vaut probablement la peine d'utiliser require("includes/helpers.php"); à la bootstrap de votre application ou de votre framework, à la place. Mais tout est relatif, tout dépend si le poids par rapport à la fréquence d'utilisation de la classe helpers vaut la peine d'être sauvegardé entre 15 et 100 lignes de require_once(). Mais si la probabilité de ne pas utiliser le fichier helpers sur une requête donnée est nul, alors require devrait définitivement appartenir à votre classe principale: avoir require_once dans chaque classe séparément constitue un gaspillage de ressources.


La fonction require_once Est utile lorsque cela est nécessaire, mais elle ne doit pas être considérée comme une solution monolithique à utiliser partout pour charger toutes les classes.

5
hexalys

Même si require_once et include_onceare plus lent que require et include (ou quelles que soient les alternatives possibles), nous parlons ici du plus petit niveau de micro-optimisation. Votre temps est bien mieux utilisé pour optimiser cette requête de boucle ou de base de données mal écrite que pour vous occuper de quelque chose comme require_once.

Maintenant, on pourrait argumenter que require_once permet des pratiques de codage médiocres, car vous n'avez pas besoin de faire attention à garder votre inclusion propre et organisée, mais cela n'a rien à voir avec la fonction elle-même et surtout pas avec sa vitesse.

Bien entendu, l'autoloading est préférable pour la propreté du code et la facilité de maintenance, mais je tiens à préciser que cela n'a rien à voir avec rapidité.

2
NeuroXc

Oui, il est légèrement plus cher que plain ol 'require (). Je pense que le fait est que si vous pouvez garder votre code suffisamment organisé pour ne pas doubler d'inclusions, n'utilisez pas les fonctions * _once (), cela vous évitera des cycles.

Mais utiliser les fonctions _once () ne va pas tuer votre application. En gros, juste ne l'utilisez pas comme une excuse pour ne pas avoir à organiser vos inclus. Dans certains cas, son utilisation est toujours inévitable et ce n'est pas grave.

0
Lucas Oman

Vous testez, en utilisant include, l'alternative de oli et __autoload (); et le tester avec quelque chose comme APC installé.

Je doute que l'utilisation constante va accélérer les choses.

0
Dinoboff