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

Discussion dans 'Développement d'un site Web ou d'une appli mobile' créé par finstreet, 23 Février 2010.

  1. finstreet
    finstreet WRInaute accro
    Inscrit:
    10 Juillet 2005
    Messages:
    13 473
    J'aime reçus:
    1
    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
     
  2. UsagiYojimbo
    UsagiYojimbo WRInaute accro
    Inscrit:
    23 Novembre 2005
    Messages:
    11 875
    J'aime reçus:
    72
    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.
     
  3. finstreet
    finstreet WRInaute accro
    Inscrit:
    10 Juillet 2005
    Messages:
    13 473
    J'aime reçus:
    1
    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.
     
  4. UsagiYojimbo
    UsagiYojimbo WRInaute accro
    Inscrit:
    23 Novembre 2005
    Messages:
    11 875
    J'aime reçus:
    72
    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.
     
  5. Nic'O
    Nic'O WRInaute discret
    Inscrit:
    13 Mai 2009
    Messages:
    180
    J'aime reçus:
    0
    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.
     
  6. finstreet
    finstreet WRInaute accro
    Inscrit:
    10 Juillet 2005
    Messages:
    13 473
    J'aime reçus:
    1
    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 :)
     
  7. Nic'O
    Nic'O WRInaute discret
    Inscrit:
    13 Mai 2009
    Messages:
    180
    J'aime reçus:
    0
    Tu as regardé comment faisait certains plugins WP ? j'apprends souvent comme ça pour l'optimisation.
     
  8. finstreet
    finstreet WRInaute accro
    Inscrit:
    10 Juillet 2005
    Messages:
    13 473
    J'aime reçus:
    1
    J'ai déjà du mal à comprendre les miens :)
     
  9. Julia41
    Julia41 WRInaute passionné
    Inscrit:
    31 Août 2007
    Messages:
    1 779
    J'aime reçus:
    0
    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 ?
     
  10. Nic'O
    Nic'O WRInaute discret
    Inscrit:
    13 Mai 2009
    Messages:
    180
    J'aime reçus:
    0
    J'aurai même tendance à dire, colle ton code ca permettra surement de voir d'un coup d'oeil les trucs qui clochent.
     
  11. finstreet
    finstreet WRInaute accro
    Inscrit:
    10 Juillet 2005
    Messages:
    13 473
    J'aime reçus:
    1
    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
     
  12. screuscreu
    screuscreu WRInaute impliqué
    Inscrit:
    14 Janvier 2008
    Messages:
    701
    J'aime reçus:
    0
    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 ?
     
  13. Julia41
    Julia41 WRInaute passionné
    Inscrit:
    31 Août 2007
    Messages:
    1 779
    J'aime reçus:
    0
    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 {}
     
  14. Julia41
    Julia41 WRInaute passionné
    Inscrit:
    31 Août 2007
    Messages:
    1 779
    J'aime reçus:
    0
    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 ?
     
  15. finstreet
    finstreet WRInaute accro
    Inscrit:
    10 Juillet 2005
    Messages:
    13 473
    J'aime reçus:
    1
    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
     
  16. Julia41
    Julia41 WRInaute passionné
    Inscrit:
    31 Août 2007
    Messages:
    1 779
    J'aime reçus:
    0
    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.
     
  17. anemone-clown
    anemone-clown WRInaute passionné
    Inscrit:
    11 Novembre 2007
    Messages:
    1 590
    J'aime reçus:
    19
    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.
     
  18. finstreet
    finstreet WRInaute accro
    Inscrit:
    10 Juillet 2005
    Messages:
    13 473
    J'aime reçus:
    1
    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 ?
     
  19. screuscreu
    screuscreu WRInaute impliqué
    Inscrit:
    14 Janvier 2008
    Messages:
    701
    J'aime reçus:
    0
    On peut aussi déjà mettre le test sur les 4 caractères dans le PHP ;) Mais oui c'est un risque ...
     
  20. jcaron
    jcaron WRInaute accro
    Inscrit:
    13 Février 2004
    Messages:
    2 593
    J'aime reçus:
    0
    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.
     
  21. finstreet
    finstreet WRInaute accro
    Inscrit:
    10 Juillet 2005
    Messages:
    13 473
    J'aime reçus:
    1
    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
     
  22. anemone-clown
    anemone-clown WRInaute passionné
    Inscrit:
    11 Novembre 2007
    Messages:
    1 590
    J'aime reçus:
    19
    jcaron a expliqué comment utiliser le Word Boundary. Perso, j'aurais mis
    Code:
    #\b$terme(s?)\b#i
    .
     
  23. jcaron
    jcaron WRInaute accro
    Inscrit:
    13 Février 2004
    Messages:
    2 593
    J'aime reçus:
    0
    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.
     
  24. finstreet
    finstreet WRInaute accro
    Inscrit:
    10 Juillet 2005
    Messages:
    13 473
    J'aime reçus:
    1
    bon je dois merdouiller, il me trouve jamais le moindre mot :)
     
  25. jcaron
    jcaron WRInaute accro
    Inscrit:
    13 Février 2004
    Messages:
    2 593
    J'aime reçus:
    0
    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.
     
  26. finstreet
    finstreet WRInaute accro
    Inscrit:
    10 Juillet 2005
    Messages:
    13 473
    J'aime reçus:
    1
    J'ai mis ca comme code :

    $contenu = preg_replace('#\b('.$leTerme.'s?)\b#i', "$1<a href='$urlGloss.php'>$2</a>$3", $contenu, 1);
     
  27. jcaron
    jcaron WRInaute accro
    Inscrit:
    13 Février 2004
    Messages:
    2 593
    J'aime reçus:
    0
    Il n'y a plus de $2 ni de $3... Logiquement il devrait faire des remplacements, mais pas comme il faut...

    Jacques.
     
  28. finstreet
    finstreet WRInaute accro
    Inscrit:
    10 Juillet 2005
    Messages:
    13 473
    J'aime reçus:
    1
    arf la burne j'avais pas fait gaffe

    Je retesterais demain, merci
     
  29. jcaron
    jcaron WRInaute accro
    Inscrit:
    13 Février 2004
    Messages:
    2 593
    J'aime reçus:
    0
    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.
     
  30. finstreet
    finstreet WRInaute accro
    Inscrit:
    10 Juillet 2005
    Messages:
    13 473
    J'aime reçus:
    1
    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 :)
     
  31. Nic'O
    Nic'O WRInaute discret
    Inscrit:
    13 Mai 2009
    Messages:
    180
    J'aime reçus:
    0
    Tu as utilisé le code juste au dessus ?
     
  32. finstreet
    finstreet WRInaute accro
    Inscrit:
    10 Juillet 2005
    Messages:
    13 473
    J'aime reçus:
    1
    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.
     
  33. Nic'O
    Nic'O WRInaute discret
    Inscrit:
    13 Mai 2009
    Messages:
    180
    J'aime reçus:
    0
    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
     
  34. finstreet
    finstreet WRInaute accro
    Inscrit:
    10 Juillet 2005
    Messages:
    13 473
    J'aime reçus:
    1
    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
     
Chargement...
Similar Threads - script PHP gourmand Forum Date
PHP : script pour mettre catalogue xml clickbank dans mysql Développement d'un site Web ou d'une appli mobile 6 Décembre 2017
Créer des graphiques en php - Vers quel script se tourner ? Développement d'un site Web ou d'une appli mobile 19 Octobre 2017
Quel package/script PHP complet de gestion Paypal ? e-commerce 13 Juin 2017
Quel script de consentement <=> tarteaucitron en pur php ? Développement d'un site Web ou d'une appli mobile 19 Août 2016
Script php rotation de bannière ordonné Développement d'un site Web ou d'une appli mobile 4 Avril 2016
Script de crawl en php Développement d'un site Web ou d'une appli mobile 3 Août 2015
Code Javascript s'affiche différemment en php et en html Développement d'un site Web ou d'une appli mobile 8 Décembre 2014
Script php Programme TV en cours Développement d'un site Web ou d'une appli mobile 30 Juillet 2014
envoyer un sms via script php Développement d'un site Web ou d'une appli mobile 25 Octobre 2013
Script d'annuaire en PHP Développement d'un site Web ou d'une appli mobile 11 Juillet 2013
  1. Ce site utilise des cookies. En continuant à utiliser ce site, vous acceptez l'utilisation des cookies.
    Rejeter la notice