Un script PHP gourmand que j'aimerais mettre au régime

WRInaute accro
Hello

Toujours dans la quête de l'amélioration de la vitesse de chargement et de la baisse de la charge serveur, j'ai un petit script PHP qui me pose problème. Le rôle de ce script est de générer des liens automatiques internes.

J'explique.

J'ai un texte que nous appellerons "Le Texte" (le terme de "Petit Parapluie" peut aussi être utilisé ou encore "Menthe Rouge"). Le Texte peut être court, ou long, ou pas très long, ou moyen, ou "oulala c'est trop long" ou encore "c'est déjà fini" ou "oh il a vraiment la taille idéale Le Texte". Bref, la taille change d'un Le Texte à l'autre. Et comme je dis toujours, c'est surtout l'intérêt du Le Texte qui compte et pas sa taille. Parce qu'il peut y avoir d'excellents Le Texte avec très peu de caractères.

De l'autre côté, j'ai un lexique que nous appellerons "Le Lexique". Le lexique comprend plus de 4.500 termes de plus de 4 caractères (en dessous c'est trop le bronx pour générer des liens).

Je lance une requete sur "Le Lexique" afin de récupérer la totalité des termes de plus de 4 caractères.

Ensuite dans une boucle, je fais un joli ereg_replace pour mettre en place les liens vers "Le Lexique" dans "Le Texte"... et là... ca rame :)

Alors le but est soit de revoir le script de fond en comble, soit de l'améliorer par quelques petites astuces. Y'a déjà une mise en cache. Ainsi cette moulinette ne tourne qu'une seule fois, pour le premier internaute qui voit Le Texte, souvent Google :)

Je viens de penser à une chose. La table contenant le lexique n'est indexée qu'au niveau de la clé. Y'aurait il à intégrer un index dans le champ Terme aussi ? Après vérif, c'est les ereg replace qui prennent du temps alors voilà à quoi ils ressemblent :

Code:
$contenu = preg_replace('#(\s|\'|>|\t)('.$leTermeGloss.')(\s|\.|s|,)#i', "$1<a href='$urlGloss.php'>$2</a>$3", $contenu, 1);

Alors si vous avez une tite piste :)

A bientôt
 
WRInaute accro
Perso j'ai un truc équivalent que je gère en CRON, et qui tourne de manière automatique la nuit. Ce CRON compare le contenu et les mots de l'index et remplis une table d'association. Ensuite sur le site, à l'appel d'un contenu je ne récupère (dans ma table d'association donc) que les mots qui y sont associés et le preg_replace fait le reste.
 
WRInaute accro
tu entends quoi par une table d'association ? Un truc du genre : ce mot a été trouvé dans ce texte et hop une ligne ?

Mais là on perd un peu l'intéret car les actus sont vite obsolètes, et faire tourner ca la nuit ferait sauter tout l'intérêt pour le plus gros des visiteurs.
 
WRInaute accro
C'est exactement ça.

Par contre étant donné qu'une fois créé les contenus sont peu mis à jour (il ne s'agit pas d'actualité pour ma part), et que la base des index évolue peut ca ne pose pas de souci.
 
WRInaute discret
Tu peux très mettre un système de cache html, que tu reset si une modification ou ajout d'un mot dans ta table.
 
WRInaute accro
non mais le cache, le reset, tout ca je fais... c'est le .. avant qui me pose problème. La première fois que les liens sont générés. La ca prend dix secondes. Sinon ca prend un dixième de seconde :)
 
WRInaute passionné
J'ignore un peu la méthode utilisée sur ce plugin wordpress (celui utilisé par WRI) qui remplace certains termes par des acronymes du terme. Ca m'a l'air d'être un peu le même principe (il va chercher dans une DB les définitions).

Tu pourrais regarder, car il m'a l'air pas mal utilisé et je pense que ça devrait pouvoir se faire simplement avec d'autres méthodes.

Sinon (mais je ne suis pas sûr de mon raisonnement). Charger le contenu des mots du lexique dans un array, et utilisation de in_array de PHP, alors on remplace.

Preg_replace est très gourmand, mais d'un autre côté, un select sur 4000 mots devrait l'être aussi.

Autre solution à tester : faire 1 select par mot en "explodant" tes "espaces" et vérifier si le mot (strlen) fait plus de 4 caractères.

Petite question, comment fais-tu ta boucle ? Avec un while, un foreach, un for ?
 
WRInaute discret
J'aurai même tendance à dire, colle ton code ca permettra surement de voir d'un coup d'oeil les trucs qui clochent.
 
WRInaute accro
Alors le code il ressemble à :

Table du lexique :
ID int(11)
Terme varchar(255)
Url varchar(250)

Le code :
Code:
$contenu = "Hello ! Comment vous allez bien ?";
$query = "SELECT ID, Terme, Url FROM lexique WHERE LENGTH(Terme) > 4  ";
$mysql_result = mysql_query($query, $mysql_link) or die("Erreur SQL : $query<br/>".mysql_error());
while ($row = mysql_fetch_array($mysql_result))
{
	$idlex = $row['ID'];
	$leTerme = $row['Terme'];
	$url = $row['Url'];
	$contenu = preg_replace('#(\s|\'|>|\t)('.$leTerme.')(\s|\.|s|,)#i', "$1<a href='$urlGloss.php'>$2</a>$3", $contenu, 1);  
}

Voili
 
WRInaute impliqué
idée à la con :
ton texte $monTexteContenu = "mon texte super bien coucou", tu le transforme en
$monContenuSplitéEnMots = "mon", "texte", "super", "bien", "coucou";
Avec ces mots tu fais un select (plus petit donc) du genre :
select id, terme, url from lexique where terme in($monContenuSplitéEnMots) and length(terme)>4

Ce qui fait que tu réduis énormément ton nombre de termes et donc ta boucle

Bonne idée ... ou je retourne dans MON code ?
 
WRInaute passionné
Je crois que preg_replace n'aime pas la concaténation mais niveau benchmark c'est de l'ordre de *10 en vitesse de traitement :
Code:
$contenu = preg_replace('#(\s|\'|>|\t)('.$leTerme.')(\s|\.|s|,)#i', $1 . "<a href=\"" . $urlGloss.php . "\">" . $2 . "</a>" . $3, $contenu, 1);

A vérifier si ça passe, je n'utilise que trop peu cette fonction.

Edit: en effet ça ne passe pas. Peut-être tenter avec des délimiteurs de type {}
 
WRInaute passionné
Et ça donne quoi en SQL un :
Code:
SELECT ID, Terme, Url FROM lexique WHERE LENGTH(Terme) > 4;
en vitesse d'execution ?
Et un :
Code:
EXPLAIN SELECT ID, Terme, Url FROM lexique WHERE LENGTH(Terme) > 4;
retourne bien des bons index ?
 
WRInaute accro
Pour screucreu ... ah j'y avais pas pensé. J'essaye d'appliquer et je reviens

Pour le SELECT, ca donne 0.0013 sec en vitesse d'exécution

Et le EXPLAIN donne :
id select_type table type possible_keys key key_len ref rows Extra
1 SIMPLE lexique ALL NULL NULL NULL NULL 5139 Using where
 
WRInaute passionné
Tu devrais créer un champ "supérieur à 4", tu fais ton calcul (UPDATE lexique SET 1 WHERE LENGTH(Terme) > 4);)
Après tu fais ton SELECT WHERE sup_a_4 = 1; ça devrait déjà être un peu plus rapide.

Mais la technique de screuscreu me semble pas mal.
 
WRInaute passionné
Bonjour,

ayant un glossaire de près de 7000 mots sur le W, j'exploite le Word Boundary (option \b qui entoure le mot) dans le preg_replace : ça accélère sacrément le truc ( divisé par 6 si on considère une longueur moyenne de 6 caractères par mot) puisqu'on ne cherche plus que des mots entiers au lieu de bouts de chaînes de caractères. En revanche, ta syntaxe risque de poser un problème avec les mots au pluriel.

Je n'utilise aucun cache, rien : tout est fait en direct et ça reste rapide.
 
WRInaute accro
Julia > Ca se joue à la marge ca non pour le nouveau champ ?

Sinon pour le découpage de mots dans le texte. Avec x milliers de caractères et énormémement de mots, ca va pas donner un IN monstrueux ?

Anemone > Pour le Word Boundary, ca se traduit comment dans ma requete ?
 
WRInaute impliqué
finstreet a dit:
Julia > Ca se joue à la marge ca non pour le nouveau champ ?

Sinon pour le découpage de mots dans le texte. Avec x milliers de caractères et énormémement de mots, ca va pas donner un IN monstrueux ?

Anemone > Pour le Word Boundary, ca se traduit comment dans ma requete ?

On peut aussi déjà mettre le test sur les 4 caractères dans le PHP ;) Mais oui c'est un risque ...
 
WRInaute accro
10 secondes? Pour quelle longeur de texte, et combien de mots dans le lexique? Sachant que la partie SQL a l'air d'être rapide, c'est la boucle qui est (très) lente à exécuter, ce qui impliquerait beaucoup de mots et un texte plutôt long.

Tu peux essayer en remplaçant ta regex par '#\b('.$terme.'s?)\b#i' (et tu remplaces par '<a href="'.$url.'">$1</a>'), mais je ne suis pas sûr que tu y gagnes beaucoup.

L'idée de réduire le select aux mots qui sont effectivement dans le texte paraît effectivement intéressante, quitte à le faire en plusieurs fois si la liste est trop longue.

Jacques.
 
WRInaute accro
le texte peut aller jusqu'à 15.000 caractères. J'ai déjà limité. Avant y'avait pas de limites. Le lexique fait plus de 5.000 mots, mais 4.500 "uniquement" sont concernés

Je viens de faire un test jcaron. C'est effectivement très rapide mais ca m'a trouvé 0 occurence. En gros c'est clair que c'est plus rapide, mais ca chope pas les pluriels et autres trucs
 
WRInaute accro
Chez moi ça marche très bien:
Code:
$contenu = preg_replace('#\b('.$leTerme.'s?)\b#i', '<a href="'.$url.'">$1</a>', $contenu, 1);

Et le "s?" est là justement pour choper le pluriel éventuel.

Jacques.
 
WRInaute accro
Copier-coller de ton code?

Un truc que tu peux essayer, mais je ne suis pas sûr que tu y gagnes, et surtout tu n'auras plus la limitation à un remplacement par mot:

- tu prends la liste de tous tes mots, et tu assembles une regex avec du genre '#\b(mot1|mot2|mot3)(s?)\b#ie' (d'un coup de implode par exemple)
- tu crées un tableau associatif qui utilise les mots comme clef et les URLs comme valeur
- et pour le remplacement tu utilises '"<a href=\\"$tableau[$1]\\">$1$2</a>"' (il est possible que je sois un peu off sur le quoting, pas l'habitude de php)
- évidemment tu ne mets pas de limite au nombre de remplacements

Jacques.
 
WRInaute accro
J'ai mis ca comme code :

$contenu = preg_replace('#\b('.$leTerme.'s?)\b#i', "$1<a href='$urlGloss.php'>$2</a>$3", $contenu, 1);
 
WRInaute accro
Et je confirme que la méthode avec les (mot1|mot2|mot3|etc.) et le tableau associatif est nettement plus rapide (4 à 5 fois je dirais), mais tu es obligé de le faire en plusieurs fois, sinon le compilateur de regex se plaint.

Avec $tab qui est un tableau associatif mot => url:
Code:
$keys   =       array_keys($tab);
$count  =       count($keys);
$nb     =       500;
for ($i = 0;$i < $count; $i+=$nb)
{
        $contenu = preg_replace('#\b('.join('|',array_slice($keys,$i,$nb)).')(s?)\b#ie', '"<a href=\"$tab[$1]\">$1$2</a>"', $contenu);
}

Maintenant, comme déjà dit, ça va remplacer toutes les occurrences de chaque mot, pas seulement la première.

La méthode la sélection des mots effectivement présents doit aussi être intéressante à explorer.

Jacques.
 
WRInaute accro
alors j'ai appliqué la méthode avec le boundary. Bon ben y'a pas photo. C'est largement plus rapide et vu que ca se charge qu'une fois, j'ai pas besoin d'optimiser +. J'ai du passer de 10 secondes à moins d'une. Vais peut être même pouvoir lever ma limitation à 15.000 caractères.

Merci à tous en tout cas :)
 
WRInaute accro
non non pas le truc qui nécessitait plusieurs passages. Juste le truc de base d'origine légèrement modifié pour des mots entiers. Ca me suffit amplement.
 
WRInaute discret
Manière de ne pas réinventer la roue, et pour rendre heureux quelques petits developpeurs comme moi, pourrai tu partager ta source, pour partir sur une truc déjà propre ?

Merci
 
WRInaute accro
Code:
$contenu = "Hello ! Comment vous allez bien ?";
$query = "SELECT ID, Terme, Url FROM lexique WHERE LENGTH(Terme) > 4  ";
$mysql_result = mysql_query($query, $mysql_link) or die("Erreur SQL : $query<br/>".mysql_error());
while ($row = mysql_fetch_array($mysql_result))
{
   $idlex = $row['ID'];
   $leTerme = $row['Terme'];
   $url = $row['Url'];
$contenu = preg_replace('#\b('.$leTerme.'s?)\b#i', "<a href='$url.php'>$1</a>", $contenu, 1);
}

Ca donne ca au final
 
Discussions similaires
Haut