web-dev-qa-db-fra.com

file_exists () est trop lent en PHP. Quelqu'un peut-il suggérer une alternative plus rapide?

Lors de l'affichage d'images sur notre site Web, nous vérifions si le fichier existe en appelant file_exists(). Nous retombons sur une image factice si le fichier était manquant.

Cependant, le profilage a montré que c’est la partie la plus lente de la génération de nos pages avec file_exists() prenant jusqu’à 1/2 ms par fichier. Nous testons seulement une quarantaine de fichiers, mais cela pousse toujours 20ms sur le temps de chargement de la page.

Quelqu'un peut-il suggérer un moyen d'accélérer le processus? Existe-t-il un meilleur moyen de vérifier si le fichier est présent? Si je construis un cache, comment le synchroniser?.

33
Rik Heywood

file_exists() devrait être une opération très peu coûteuse. Notez également que file_exists construit son propre cache pour améliorer les performances.

Voir: http://php.net/manual/en/function.file-exists.php

24
RC.

Utilisez des chemins absolus! En fonction de votre paramètre include_path PHP vérifie tous (!) Ces répertoires si vous vérifiez les chemins de fichiers relatifs! Vous pouvez désélectionner include_path temporairement avant de vérifier l'existence. 

realpath() fait la même chose mais je ne sais pas si c'est plus rapide.

Mais l'accès aux fichiers d'E/S est toujours lent. Un accès au disque durESTplus lent que le calcul normal du processeur. 

20
powtac

Le moyen le plus rapide de vérifier l'existence d'un fichier local est stream_resolve_include_path () :

if (false !== stream_resolve_include_path($s3url)) { 
  //do stuff 
}

Résultats de performance stream_resolve_include_path () vs file_exists () :

Test name       Repeats         Result          Performance     
stream_resolve  10000           0.051710 sec    +0.00%
file_exists     10000           0.067452 sec    -30.44%

Dans test utilisé des chemins absolus. La source du test est ici . Version PHP:

PHP 5.4.23-1 ~ dotdeb.1 (cli) (construit le 13 décembre 2013 21:53:21)
Copyright (c) 1997-2013 Le groupe PHP
Zend Engine v2.4.0, Copyright (c) 1998-2013 Zend Technologies

17

Nous retombons sur une image factice s'il manquait le fichier.

Si vous souhaitez simplement revenir à cette image factice, vous pouvez envisager de laisser le client négocier avec le serveur au moyen d'une redirection (vers l'image factice) sur le fichier introuvable.

De cette façon, vous aurez juste un peu de temps système de redirection et un retard non perceptible du côté client. Au moins, vous vous débarrasserez de l'appel "coûteux" (ce qui n'est pas le cas, je le sais) à file_exists.

Juste une pensée.

11
jensgram

Tests d'évaluation avec PHP 5.6:

Fichier existant:

0.0012969970 : stream_resolve_include_path + include  
0.0013520717 : file_exists + include  
0.0013728141 : @include  

Fichier non valide:

0.0000281333 : file_exists + include  
0.0000319480 : stream_resolve_include_path + include  
0.0001471042 : @include  

Dossier invalide:

0.0000281333 : file_exists + include  
0.0000360012 : stream_resolve_include_path + include  
0.0001239776 : @include  

Code:

// microtime(true) is less accurate.
function microtime_as_num($microtime){
  $time = array_sum(explode(' ', $microtime));
  return $time;
}

function test_error_suppression_include ($file) {
  $x = 0;
  $x = @include($file);
  return $x;
}

function test_file_exists_include($file) {
  $x = 0;
  $x = file_exists($file);
  if ($x === true) {
    include $file;
  }
  return $x;
}

function test_stream_resolve_include_path_include($file) {
  $x = 0;
  $x = stream_resolve_include_path($file);
  if ($x !== false) {
    include $file;
  }
  return $x;
}

function run_test($file, $test_name) {
  echo $test_name . ":\n";
  echo str_repeat('=',strlen($test_name) + 1) . "\n";

  $results = array();
  $dec = 10000000000; // digit precision as a multiplier

  $i = 0;
  $j = 0;
  $time_start = 0;
  $time_end = 0;
  $x = -1;
  $time = 0;

  $time_start = microtime();
  $x= test_error_suppression_include($file);
  $time_end = microtime();
  $time = microtime_as_num($time_end) - microtime_as_num($time_start);

  $results[$time*$dec] = '@include';

  $i = 0;
  $j = 0;
  $time_start = 0;
  $time_end = 0;
  $x = -1;
  $time = 0;

  $time_start = microtime();
  $x= test_stream_resolve_include_path_include($file);
  $time_end = microtime();
  $time = microtime_as_num($time_end) - microtime_as_num($time_start);

  $results[$time * $dec] = 'stream_resolve_include_path + include';

  $i = 0;
  $j = 0;
  $time_start = 0;
  $time_end = 0;
  $x = -1;
  $time = 0;

  $time_start = microtime();
  $x= test_file_exists_include($file);
  $time_end = microtime();
  $time = microtime_as_num($time_end) - microtime_as_num($time_start);

  $results[$time * $dec ] = 'file_exists + include';

  ksort($results, SORT_NUMERIC);

  foreach($results as $seconds => $test) {
    echo number_format($seconds/$dec,10) . ' : ' . $test . "\n";
  }
  echo "\n\n";
}

run_test($argv[1],$argv[2]);

Exécution en ligne de commande:  

php test.php '/path/to/existing_but_empty_file.php' 'Existing File'  
php test.php '/path/to/non_existing_file.php' 'Invalid File'  
php test.php '/path/invalid/non_existing_file.php' 'Invalid Folder'  
5
Jonathan Falkner

file_exists() est automatiquement mis en cache par PHP. Je ne pense pas que vous trouverez une fonction plus rapide dans PHP pour vérifier l'existence d'un fichier.

Voir ce fil .

3
mculp

Créez une routine de hachage pour partager les fichiers en plusieurs sous-répertoires.

filename.jpg -> 012345 -> /01/23/45.jpg 

Vous pouvez également utiliser mod_rewrite pour renvoyer votre image de marque de réservation pour les demandes envoyées à votre répertoire 404.

3
racerror

Je ne sais pas exactement ce que vous voulez faire, mais vous pourriez simplement laisser le client s'en charger .

2
ViperArrow

Si vous ne faites que vérifier la files existante, utilisez is_file(). file_exists() recherche un répertoire de fichiers existant OR, de sorte que is_file() pourrait être un peu plus rapide.

2
Alex

Sont-ils tous dans le même répertoire? Si c'est le cas peutpeut valoir la peine d’obtenir la liste des fichiers et de les stocker dans un hachage et de les comparer avec celle-ci plutôt que toutes les recherches file_exists. 

1
easement

Si vous voulez vérifier l'existence d'un fichier image, un beaucoup plus rapide moyen consiste à utiliser getimagesize !

Plus rapide localement et à distance!

if(!@GetImageSize($image_path_or_url)) // False means no imagefile
 {
 // Do something
 }
1
Fabrice Oppitz

Vieille question, je vais ajouter une réponse ici. Pour php 5.3.8, is_file () (pour un fichier existant) est un ordre de grandeur plus rapide. Pour un fichier non existant, les temps sont presque identiques. Pour PHP 5.1 avec eaccelerator, ils sont un peu plus proches. 

PHP 5.3.8 avec et sans APC

time ratio (1000 iterations)
Array
(
    [3."is_file('exists')"] => 1.00x    (0.002305269241333)
    [5."is_link('exists')"] => 1.21x    (0.0027914047241211)
    [7."stream_resolve_inclu"(exists)] => 2.79x (0.0064241886138916)
    [1."file_exists('exists')"] => 13.35x   (0.030781030654907)
    [8."stream_resolve_inclu"(nonexists)] => 14.19x (0.032708406448364)
    [4."is_file('nonexists)"] => 14.23x (0.032796382904053)
    [6."is_link('nonexists)"] => 14.33x (0.033039808273315)
    [2."file_exists('nonexists)"] => 14.77x (0.034039735794067)
)

PHP 5.1 avec accélérateur

time ratio (1000x)
Array
(
    [3."is_file('exists')"] => 1.00x    (0.000458002090454)
    [5."is_link('exists')"] => 1.22x    (0.000559568405151)
    [6."is_link('nonexists')"] => 3.27x (0.00149989128113)
    [4."is_file('nonexists')"] => 3.36x (0.00153875350952)
    [2."file_exists('nonexists')"] => 3.92x (0.00179600715637)
    [1."file_exists('exists"] => 4.22x  (0.00193166732788)
)

Il y a quelques mises en garde.
1) Tous les "fichiers" ne sont pas des fichiers, les tests is_file () pour regular , pas des liens symboliques. Ainsi, sur un système * nix, vous ne pouvez pas vous contenter de is_file () à moins que vous ne soyez sure que vous ne traitiez que des fichiers normaux. Pour les téléchargements, etc., cela peut être une hypothèse juste, ou si le serveur est basé sur Windows, qui n'a pas réellement de lien symbolique. Sinon, vous devrez tester is_file($file) || is_link($file)

2) Les performances se dégradent définitivement pour toutes les méthodes si le fichier est manquant et devient à peu près égal.

3) La plus grande mise en garde. Toutes les méthodes mettent en cache les statistiques du fichier pour accélérer la recherche. Par conséquent, si le fichier change régulièrement ou rapidement, il doit être supprimé, réapparu, puis supprimé, puis clearstatcache(); doit être exécuté pour garantir que les informations d'existence de fichier correctes sont stockées dans le cache. Alors j'ai testé ceux-ci. J'ai oublié tous les noms de fichiers et autres. L'important est que presque tout le temps converge, sauf stream_resolve_include, qui est 4x aussi rapide. Encore une fois, ce serveur a eaccelerator dessus, donc YMMV.

time ratio (1000x)
Array
(
    [7."stream_resolve_inclu...;clearstatcache();"] => 1.00x    (0.0066831111907959)
    [1."file_exists(...........;clearstatcache();"] => 4.39x    (0.029333114624023)
    [3."is_file(................;clearstatcache();] => 4.55x    (0.030423402786255)
    [5."is_link(................;clearstatcache();] => 4.61x    (0.030798196792603)
    [4."is_file(................;clearstatcache();] => 4.89x    (0.032709360122681)
    [8."stream_resolve_inclu...;clearstatcache();"] => 4.90x    (0.032740354537964)
    [2."file_exists(...........;clearstatcache();"] => 4.92x    (0.032855272293091)
    [6."is_link(...............;clearstatcache();"] => 5.11x    (0.034154653549194)
)

En gros, l’idée est la suivante: si vous êtes sûr à 100% qu’il s’agit d’un fichier, et non d’un lien symbolique ou d’un répertoire, et qu’il existera probablement, il existera, utilisez is_file(). Vous verrez un gain certain. Si le fichier peut être un fichier ou un lien symbolique à un moment quelconque, is_file () 14x + is_link () 14x (is_file() || is_link()) a échoué et finit par être 2x plus lent. Si l'existence du fichier change BEAUCOUP, utilisez stream_resolve_include_path ().

Cela dépend donc de votre scénario d'utilisation.

1
Beracah

Qu'en est-il de glob()? Mais je ne sais pas si c'est rapide.

http://www.php.net/manual/en/function.glob.php

0
juno

Je trouve 1/2ms par appel très, très abordable. Je ne pense pas qu'il existe des alternatives beaucoup plus rapides, car les fonctions de fichier sont très proches des couches inférieures qui gèrent les opérations de fichier. 

Vous pouvez toutefois écrire un fichier wrapper dans file_exists () qui met en cache les résultats dans un memcache ou une installation similaire. Cela devrait réduire le temps passé à presque rien dans l'utilisation quotidienne.

0
Pekka 웃

Lorsque vous enregistrez un fichier dans un dossier, si le téléchargement a réussi, vous pouvez stocker le chemin d'accès à une table de base de données.

Il vous suffira ensuite d'interroger la base de données pour trouver le chemin du fichier demandé.

0
Galois

Je pense que le meilleur moyen est de conserver l'URL de l'image dans la base de données, puis de la placer dans une variable de session, en particulier lorsque vous avez une authentification. De cette façon, vous ne devez pas vérifier chaque fois qu'une page est rechargée.

0
daRula

Je suis venu sur cette page à la recherche d'une solution, et il semble que fopen puisse faire l'affaire. Si vous utilisez ce code, vous souhaiterez peut-être désactiver la journalisation des erreurs pour les fichiers introuvables.

<?php
for ($n=1;$n<100;$n++){
clearstatcache();
$h=@fopen("files.php","r");
if ($h){
echo "F";
fclose($h);
}else{
echo "N";
}
}
?>
0
user4064342

Vous pouvez faire une tâche cron pour créer périodiquement une liste d’images et les stocker dans la base de données/fichier/BDB/...

Toutes les demi-heures devraient suffire, mais assurez-vous de créer une interface pour réinitialiser le cache en cas d’ajout/suppression de fichiers.

Et puis, il est également facile de lancer la recherche. -mmin -30 -print0 sur le shell et ajouter de nouveaux fichiers.