OVH Cloud OVH Cloud

La meilleure facon de coder ?

5 réponses
Avatar
Luc
Bonjour, j'aurais besoin des conseils de codeurs chevronnés pour
améliorer ma production !

J'utilise PHP pour analyser en local des fichiers de logs Apache, ça
n'est sans doute pas l'idéal mais ça me permet de cibler exactement
certaines infos.
Je voudrais savoir quel est le "style" de programmation le plus efficace
parce que pour l'instant ça marche mais en se traînant, à tel point que
même avec un set_time_limit à 0, j'obtiens souvent une page blanche.

Voilà ce que je fais :
- ouverture du fichier de logs de plusieurs Mo avec fopen,
- boucle dessus pour lire chaque ligne avec fgets,
- traitement de chaque ligne,
- fermeture du fichier,
- affichage des infos.

Et les questions que je me pose :
- fopen/fgets par rapport à file : dans les notes de la doc officielle
(à la page de "file"), il y a une qui dit que fgets est beaucoup plus
rapide, est-ce que c'est vrai ?
- est-ce qu'il vaut mieux mettre toutes les lignes du fichier lu dans un
tableau et traiter ensuite chaque item de ce tableau ou bien, comme j'ai
fait, traiter les lignes une par une pour éviter d'avoir un tableau qui
fait plusieurs Mo ? Ou alors ça revient au même ?
- enfin, comme j'utilise dans mon script pas mal d'expressions
rationnelles et que la doc précise qu'il vaut mieux utiliser strpos()
pour tester la présence d'une chaîne, je voudrais aussi savoir s'il y a
une différence de vitesse apréciable entre :
if (ereg("^(www\.)?machin",$url)) {...}
if (strpos($url,"www.machin")!==0 && strpos($url,"machin")!==0) {...}

Voilà, merci d'avance pour vos conseils...

5 réponses

Avatar
Frederic BISSON
Hello !
Bonjour, j'aurais besoin des conseils de codeurs chevronnés pour
améliorer ma production !
Tu veux améliorer la qualité du code produit ou optimiser ton code ? Ce

n'est pas vraiment la même chose.

J'utilise PHP pour analyser en local des fichiers de logs Apache, ça
n'est sans doute pas l'idéal mais ça me permet de cibler exactement
certaines infos.
Ouille ouille ouille...

L'est pas vraiment fait pour ça notre brave PHP.

Je voudrais savoir quel est le "style" de programmation le plus efficace
parce que pour l'instant ça marche mais en se traînant, à tel point que
même avec un set_time_limit à 0, j'obtiens souvent une page blanche.
N'aurais-tu pas plus vite fait d'appeler une commande externe (style

grep, sed ou encore un script shell) que de le coder complètement en PHP ?

Voilà ce que je fais :
- ouverture du fichier de logs de plusieurs Mo avec fopen,
- boucle dessus pour lire chaque ligne avec fgets,
- traitement de chaque ligne,
- fermeture du fichier,
- affichage des infos.
Un bout de code aurait été plus pratique, non ?

Cela nous aiderait à comprendre quels sont réellement tes besoins et
quelles sont les solutions les plus appropriées.

Et les questions que je me pose :
- fopen/fgets par rapport à file : dans les notes de la doc officielle
(à la page de "file"), il y a une qui dit que fgets est beaucoup plus
rapide, est-ce que c'est vrai ?
Pas vraiment.


Il suffit simplement de tester :

<?php
ini_set('memory_limit','50M');
define('FICHIER','/chemin/vers/access_log');

// Avec file
$tableau=file(FICHIER);
foreach($tableau as $ligne) {
}

// Avec fgets
$fichier=fopen(FICHIER,"r");
while(!feof($fichier)) {
$ligne=fgets($fichier,65536);
}
fclose($fichier);
?>

Machine de test:
- bi-Pentium Pro 200 MHz, 512 Ko cache, 128 Mo de RAM,
- noyau 2.6.9
- PHP 5.0.2 (compilation manuelle optimisée pour la machine)
- Fichier log de 14 Mo, plus de 55000 lignes
- commande de test: time php testlecture.php

Les tests sont lancés plusieurs fois de suite (chargement du fichier en
mémoire cache) dans le but de s'affranchir des limitations du disque
(hdparm -t le donne à 10 Mo/s, la lecture complète du fichier depuis
le disque demande donc un minimum de 1,4 s dans le meilleur des cas).

file+foreach:
- total 3.4s
- PHP 2.7s
- système 0.7s

fgets:
- total 16.0s
- PHP 15.4s
- système 0.6s

On peut se poser la question de savoir si la longueur limite de 65536 pour
fgets est justifiée car la passer de 65536 à 32768 permet de quasiment
diviser par deux le temps total. Par contre, si je n'ai pas une valeur
aussi élevée, je n'obtiens pas le même nombre de lignes car les logs
d'Apache peuvent contenir plus de 32 Ko par ligne.

Toujours est-il que file+foreach est plus rapide que fgets.

- est-ce qu'il vaut mieux mettre toutes les lignes du fichier lu dans un
tableau et traiter ensuite chaque item de ce tableau ou bien, comme j'ai
fait, traiter les lignes une par une pour éviter d'avoir un tableau qui
fait plusieurs Mo ? Ou alors ça revient au même ?
Si tu peux régler les paramètres mémoires exclusivement pour ce script,

file permettra de gagner du temps. Si tu travailles en lecture
ligne par ligne, ton script va introduire de très nombreux appels
entrée/sortie système ainsi que de très nombreux appels interne pour la
gestion des données et des structures de données qui s'ajouteront au
temps d'exécution.

L'occupation mémoire est nécessairement un problème. Par défaut, PHP
doit être configuré à 8 Mo de mémoire par processus. Avec un fichier
de log de plusieurs Mo, cette limite est rapidement atteinte.

- enfin, comme j'utilise dans mon script pas mal d'expressions
rationnelles et que la doc précise qu'il vaut mieux utiliser strpos()
pour tester la présence d'une chaîne, je voudrais aussi savoir s'il y a
une différence de vitesse apréciable entre :
if (ereg("^(www.)?machin",$url)) {...}
if (strpos($url,"www.machin")!==0 && strpos($url,"machin")!==0) {...}
Dans les mêmes conditions de tests que précédemment et en rajoutant les

lignes suivantes

<?php
foreach($tableau as $ligne) {
// if(ereg("^(www.)?machin",$ligne)) $compteur++;
// if(strpos($ligne,"www.machin")!==0 && strpos($ligne,"machin")!==0) $compteur++;
}
?>

Je n'ai pu avoir une différence que de l'ordre du centième de seconde.
Donc peu flagrant.
1 strpos est plus rapide qu'1 ereg, c'est sûr.
Mais 2 strpos, ça l'est beaucoup moins.

@+

Frédéric BISSON

Avatar
loufoque
Luc a dit le 09/12/2004 22:10:
- fopen/fgets par rapport à file : dans les notes de la doc officielle
(à la page de "file"), il y a une qui dit que fgets est beaucoup plus
rapide, est-ce que c'est vrai ?


file() est plus rapide, enfin tout est relatif.
Pour un gros fichier, je ne pense pas qu'il soit pertinent d'allouer
tant de mémoire RAM.
Je ferais plutôt avec fgets(), qui permet donc de n'avoir en mémoire que
le contenu de la ligne en cours.

- est-ce qu'il vaut mieux mettre toutes les lignes du fichier lu dans un
tableau et traiter ensuite chaque item de ce tableau ou bien, comme j'ai
fait, traiter les lignes une par une pour éviter d'avoir un tableau qui
fait plusieurs Mo ? Ou alors ça revient au même ?


Moi je les traiterais une par une pour éviter d'avoir un tableau qui
fait plusieurs Mo, oui.

- enfin, comme j'utilise dans mon script pas mal d'expressions
rationnelles et que la doc précise qu'il vaut mieux utiliser strpos()
pour tester la présence d'une chaîne, je voudrais aussi savoir s'il y a
une différence de vitesse apréciable entre :
if (ereg("^(www.)?machin",$url)) {...}
if (strpos($url,"www.machin")!==0 && strpos($url,"machin")!==0) {...}


Avec strpos() c'est bien mieux.

Avatar
Luc
Frederic BISSON wrote:
Hello !
Salut et merci de ta réponse !

Tu veux améliorer la qualité du code produit ou optimiser ton code ? Ce
n'est pas vraiment la même chose.
Un peu des deux ! Vu que je n'ai pas trop d'idées pour optimiser, je me

dis que c'est peut-être la méthode qui pèche...
Ouille ouille ouille...
L'est pas vraiment fait pour ça notre brave PHP.
N'aurais-tu pas plus vite fait d'appeler une commande externe (style
grep, sed ou encore un script shell) que de le coder complètement en PHP ?
J'imagine bien que PHP n'est pas l'idéal pour faire ce que je veux mais

comme je l'utilise en local je pense que ça n'est pas trop génant de
monopoliser le serveur et surtout je ne sais pas faire avec les
solutions que tu proposes ! C'est vrai que c'est plus une solution par
défaut.

Un bout de code aurait été plus pratique, non ?
C'est vrai ! A voir tout en bas.


Il suffit simplement de tester :
Merci pour les tests qui ont bien éclairés bien ma petite lanterne, je

suis passé à file() avec un foreach(...) sur le tableau et ça semble en
effet moins peiner.

Par contre j'ai encore deux/trois petites questions :
Dans ta procédure de test, tu mets :
ini_set('memory_limit','50M');
Si je comprends bien, ça signifie que tu alloues 50 mégas à l'opération?
D'après la doc, le paramètre "memory_limit" n'est modifiable que si PHP
a été compilé avec "--enable-memory-limit" ce qui n'est malheureusement
pas le cas ici (et je suis totalement incapable de compiler PHP par
moi-même). Du coup mon PHP est limité à 8 Mo? et dans ce cas il n'y a
pas d'autre méthode pour lui allouer plus de mémoire ?

Sinon j'ai remarqué que quand mon script merdoie (je n'obtiens au bout
du compte qu'à une page blanche), ça correspond plus ou moins au timeout
d'Apache (300 secondes). Comme j'ai déjà set_time_limit(0), est-ce que
je ne pourrais pas augmenter le timeout ?
Je suis le seul utilisateur du serveur et ça ne me dérange pas que la
machine tourne pendant 10 minutes si il faut pour pondre ses résultats.

Voilà, et encore merci pour ta réponse, tu maîtrises visiblement mieux
que moi le sujet !

Luc

Mon script :
(Il a trois fonctions :
- récupérer les referes
- pour ceux qui viennent de chez Google, récupérer les mots clés
recherchés associés à la page appelée
- suivre de plus près certaines pages)

$referers = array();
// pour stocker les referers autres que Google
$recherche = array();
// pour stocker les infos de Google : URL/mots cles
$achat_cpt = array(0,0,0,0,0);
$achat_ref = array();
// initialisation des variables pour suivi des achats

(...)

// Traitement fichier log
-----------------------------------------------------
$lefich = file($diff[$i]);
$reqInsert = 'INSERT INTO searchterms VALUES ';
foreach ($lefich as $line_num => $line) {
// Recherche les lignes qui contiennent un referer
if (eregi("(GET|POST) ([^ ]+).*"http://([^"]+)",$line,$regs)) {
if (!eregi("^(www.)?monsite",$regs[3]) &&
!eregi("pagead2.googlesyndication",$regs[3])) {
// ce n'est ni une reference interne ni un appel pour une pub google
// on a les referers externes ($regs[3]) et $regs[1]=>page appelee
if (eregi("^(www.)?google.",$regs[3])) {
// 1) traitement pour ce qui vient de chez Google
$termes = termesGoogle($regs[3]);
$url = $regs[2];
$recherche[] = array($termes,$url);
// stockage des requetes pour la table 'searchterms"
if (strpos($termes,"cache:")!==0) {
// on ne tient pas compte des references au cache
$reqInsert .= "('','$url','$termes','$ladate'),";
}
} else {
// 2° Autres referers
$referers[] = $regs[3];
}
}
}
// Recupere les infos sur les pages /achat/
if (eregi("(GET|POST) /achat",$line)) {
if (eregi("(GET|POST) /achat/ HTTP",$line)) {
$achat_cpt[0] ++;
// recupere le referer et le met apres $achat_ref
$pattern = "/"(http://.*)"/U";
$replacement = "<span class="referer">$1</span>";
$achat_ref[] = preg_replace($pattern, $replacement, $line);
} elseif (strpos($line,"POST /achat/choix.php")!==0) {
$achat_cpt[1] ++;
} elseif (strpos($line,"POST /achat/confirm.php")!==0) {
$achat_cpt[2] ++;
} elseif (eregi("GET /achat/tour/(index.php)?",$line)) {
$achat_cpt[3] ++;
}
}
}
// enregistre le nom du fichier dans la table 'logs'
$requete = "INSERT INTO logs VALUES('$diff[$i]','$ladate')";
$resultat = execRequete($requete,$connexion);
// stockage des requetes dans 'searchterms"
if ($reqInsert[strlen($reqInsert)-1]==',')
$reqInsert[strlen($reqInsert)-1]=';';
$resultat = execRequete($reqInsert,$connexion);

// Tri des resultats
sort($recherche);
sort($referers);

Avatar
bertrand
Frederic BISSON wrote:

Sinon j'ai remarqué que quand mon script merdoie (je n'obtiens au bout
du compte qu'à une page blanche), ça correspond plus ou moins au timeout
d'Apache (300 secondes). Comme j'ai déjà set_time_limit(0), est-ce que
je ne pourrais pas augmenter le timeout ?
Je suis le seul utilisateur du serveur et ça ne me dérange pas que la
machine tourne pendant 10 minutes si il faut pour pondre ses résultats.



Bonjour,

Si tu exécute cela en local, pourquoi ne fais tu pas tourner ton script
php en ligne de commande pour générer le résultat dans un fichier html
statique. Tu n'a plus aucune contrainte de timeout dans cette utilisation.

La méthode de tête :
php -q monscript.php > mon_rapport.html

Tu met cela dans un script que tu exécutes à la fréquence de
récupération de ton fichier de log (avec éventuellement nommage du
fichier rapport en conséquence)

Cordialement

--
Bertrand Perrotte

Webmaistre <url:http://canoe.kayak.free.fr/>
secrétaire du Canoë Kayak Gennevilliers

Avatar
Luc
bertrand wrote:
Si tu exécute cela en local, pourquoi ne fais tu pas tourner ton script
php en ligne de commande pour générer le résultat dans un fichier html
statique. Tu n'a plus aucune contrainte de timeout dans cette utilisation.
Pourquoi ?

Parce que je n'y avais pas pensé et que c'est un truc que je ne maîtrise
pas du tout mais c'est vrai que ça semble être l'idéal dans ce cas.

La méthode de tête :
php -q monscript.php > mon_rapport.html
OK, (par contre je n'ai rien trouvé dans la doc sur "-q", tu pensais

peut-être à "-f" ?)

D'après mes premiers tests (simples) ça fonctionne super, le gros soucis
quand j'essaye avec mon script, ce sont les includes et la communication
avec MySQL : je ne sais pas comment faire pour indiquer les bons chemins
pour PHP en ligne de commande. Je me disais naïvement qu'il suffirait
d'exécuter la commande depuis le dossier où se trouve le script, mais ça
ne fonctionne pas et la doc est assez onscure pour moi.

Si tu connais quelques ressources sur la question de PHP en CLI, je suis
preneur !

Tu met cela dans un script que tu exécutes à la fréquence de
récupération de ton fichier de log (avec éventuellement nommage du
fichier rapport en conséquence)

Cordialement

Merci