Clusters : une utilisation performante des ensembles enregistrés

Transcription

Clusters : une utilisation performante des ensembles enregistrés
Clusters : une utilisation performante des ensembles enregistrés Partie 2
Par
Kent D. WILBUR, Manager of Information System, 4D Inc.
Note technique 4D-200601-02-FR
Version 1
Date 1 janvier 2006
Résumé
Voici la seconde note sur l’utilisation des clusters (ensembles enregistrés) pour améliorer radicalement les
performances d’une base. La première traitait de la création et de la maintenance des clusters. Celle-ci traite
l’utilisation des clusters pour la recherche.
4D Notes techniques
Copyright © 1985-2006 4D SA - Tous droits réservés
Tous les efforts ont été faits pour que le contenu de cette note technique présente le maximum de fiabilité possible.
Néanmoins, les différents éléments composant cette note technique, et le cas échéant, le code, sont fournis sans garantie d'aucune sorte.
L'auteur et 4D S.A. déclinent donc toute responsabilité quant à l'utilisation qui pourrait être faite de ces éléments, tant à l'égard de leurs
utilisateurs que des tiers.
Les informations contenues dans ce document peuvent faire l'objet de modifications sans préavis et ne sauraient en aucune manière engager 4D
SA. La fourniture du logiciel décrit dans ce document est régie par un octroi de licence dont les termes sont précisés par ailleurs dans la licence
électronique figurant sur le support du Logiciel et de la Documentation afférente. Le logiciel et sa documentation ne peuvent être utilisés, copiés
ou reproduits sur quelque support que ce soit et de quelque manière que ce soit, que conformément aux termes de cette licence.
Aucune partie de ce document ne peut être reproduite ou recopiée de quelque manière que ce soit, électronique ou mécanique, y compris par
photocopie, enregistrement, archivage ou tout autre procédé de stockage, de traitement et de récupération d'informations, pour d'autres buts que
l'usage personnel de l'acheteur, et ce exclusivement aux conditions contractuelles, sans la permission explicite de 4D SA.
4D, 4D Calc, 4D Draw, 4D Write, 4D Insider, 4ème Dimension ®, 4D Server, 4D Compiler ainsi que les logos 4e Dimension, sont des marques
enregistrées de 4D SA.
Windows,Windows NT,Win 32s et Microsoft sont des marques enregistrées de Microsoft Corporation.
Apple, Macintosh, Power Macintosh, LaserWriter, ImageWriter, QuickTime sont des marques enregistrées ou des noms commerciaux de Apple
Computer,Inc.
Mac2Win Software Copyright © 1990-2002 est un produit de Altura Software,Inc.
4D Write contient des éléments de "MacLink Plus file translation", un produit de DataViz, Inc,55 Corporate drive,Trumbull,CT,USA.
XTND Copyright 1992-2002 © 4D SA. Tous droits réservés.
XTND Technology Copyright 1989-2002 © Claris Corporation.. Tous droits réservés ACROBAT © Copyright 1987-2002, Secret Commercial
Adobe Systems Inc.Tous droits réservés. ACROBAT est une marque enregistrée d'Adobe Systems Inc.
Tous les autres noms de produits ou appellations sont des marques déposées ou des noms commerciaux appartenant à leurs propriétaires
respectifs.
1 / 11
Clusters : une utilisation performante des ensembles enregistrés Partie 2
Introduction
Voici la seconde note sur l’utilisation des clusters (ensembles enregistrés) pour améliorer radicalement les
performances d’une base. La première traitait de la création et de la maintenance des clusters. Celle-ci traite
l’utilisation des clusters pour la recherche.
Brièvement résumé, l’objectif est de permettre une recherche rapide de mots-clés ou même d’expressions
situées n’importe où dans le titre ou le champ texte de la table [BlocsTextes] dans la base exemple. Chaque
mot isolé qui ne figure pas dans la liste des exclusions est analysé et enregistré dans la table [Mots]. Les
clusters sont des tableaux booléens qui indiquent quels enregistrements comportent un mot particulier. Ils
sont sauvegardés dans des BLOBs de la table [Mots].
Les clusters sont-ils vraiment plus rapides ?
La réponse est simple, oui ! Mais pour ceux d’entre vous qui en doutent encore, la base exemple propose
plusieurs tests. Ouvrez la base et, dans le menu Fichier, choisissez Blocs de texte. Ceci affichera tous les
enregistrements de la table [BlocsTexte] et activera la ligne Cluster démo du menu Démo.
Le premier enregistrement de la base a pour titre « Upgrading to 4D Server 6.5.1 ». (Oui, je sais, les données
sont obsolètes.). Dans cet enregistrement se trouve l’expression « upgrading and converting ». Cette
occurrence n’existe que pour ce seul enregistrement.
Faisons quelques tests. Si vous avez ouvert l’enregistrement pour vérifier le texte, quittez et rouvrez la base
pour vous assurer qu’il n’y a rien dans le cache pour le premier test.
Voici le tableau des résultats que j’ai obtenus en recherchant l’expression « upgrading and converting ».
2 / 11
La recherche par cluster a donné le même temps avec ou sans cache. C’est une base relativement réduite, de
moins de 6000 enregistrements. Dans une base plus importante, la recherche par « contient » prendrait
encore plus de temps, alors que le temps de recherche par cluster resterait virtuellement identique. Pour la
recherche par « est égal à », ceci représente un gain de 99,67 %.
Donc, ma réponse aujourd’hui est : les clusters peuvent vraiment accélérer significativement ma base avec
un peu plus de codage et un plus gros disque dur.
Note : Les valeurs ci-dessus sont des moyennes sur plusieurs tests. Elles varient d’une machine à l’autre et
selon les critères de recherche. Mais elles sont bien représentatives des résultats attendus.
Que se passe-t-il dans le code ?
La première requête est une simple recherche par contenu directement dans les champs. C’est quelque chose
qui se pratique souvent dans les bases sans clusters.
Pour les autres requêtes, les mots cherchés ont été analysés dans un tableau et comparés à la liste des mots
exclus (Voir partie 1 pour une explication sur les mots exclus). Après le tri, on construit des tableaux de
mots, chaque élément de tableau, un mot unique, est trouvé dans la table [Mots]. Le tableau booléen stocké
dans la table [Mots] est chargé en mémoire et un ensemble est créé à partir du tableau booléen. Puis, selon la
recherche par OU, ET ou Contient, un nouvel ensemble est créé par la commande REUNION ou
INTERSECTION.
Dans le cas de la recherche par Contient, les enregistrements obtenus avec l’INTERSECTION sont ensuite
explorés par une recherche traditionnelle sur les champs eux-mêmes. La différence est que cette recherche ne
concerne que les 7 enregistrements qui contiennent à la fois « upgrading » et « converting » au lieu de toute
la table. Le résultat est un gain de temps de plus de 99%.
La méthode M_ClusterQuery est attachée au menu Demo de la barre de menu Demo. Elle affiche le
dialogue et passe les bons paramètres au code correspondant au choix dans le dialogue.
Si (Faux)
` Méthode: M_ClusterQuery
` Created by: Kent Wilbur
` Objet: Démonstration de la recherche par cluster
Fin de si
` Déclaration des variables locales
C_ENTIER LONG($LEndTime)
C_ENTIER LONG($LPid)
C_ENTIER LONG($LStartTime)
C_POINTEUR($pTable)
3 / 11
$LWindowID:=Creer fenetre formulaire([zDialogs];"QueryDemo";Dialogue modal déplaçable ;
Centrée horizontalement ;Centrée verticalement ;*)
DIALOGUE([zDialogs];"QueryDemo")
FERMER FENETRE($LWindowID)
Si (OK=1)
$LStartTime:=Nombre de millisecondes
Au cas ou
: (Longueur(tQueryText)=0)
ALERTE("Vous devez entrer une valeur à rechercher.")
: (rb1=1) ` Ancienne manière de faire une recherche par contenu
CHERCHER([BlocsTextes];[BlocsTextes]Titre="@"+tQueryText+"@";*)
CHERCHER([BlocsTextes]; | ;[BlocsTextes]ZoneTexte="@"+tQueryText+"@")
Sinon
$pTable:=->[BlocsTextes] ` C'est la seule table pour cette base, mais on pourrait en gérer d'autres ici
Au cas ou
: (rb3=1) ` Nouvelle manière de faire une recherche par contenu
CLUSTER_DoQuery ($pTable;tQueryText;"Contains")
: (sb1=1) ` Recherche ET
CLUSTER_DoQuery ($pTable;tQueryText;"And")
Sinon
CLUSTER_DoQuery ($pTable;tQueryText;"OR")
Fin de cas
Au cas ou
: (Taille tableau(atQueryValues)=0)
ALERTE("La valeur Entrée ne contient que des mots non indexés\rMerci d'entrer une autre
ex"+"pression.")
: (Enregistrements trouves($pTable->)=0)
ALERTE("Aucun enregistrement trouvé.")
Fin de cas
Fin de cas
$LEndTime:=Nombre de millisecondes
ALERTE("Temps de recherche : "+Chaine($LEndTime-$LStartTime)+" milliseconde(s) pour "
+Chaine(Enregistrements trouves([BlocsTextes]))+" enregistrement(s) trouvé(s.")
WIN_OutputWindowTitle
Fin de si
` Fin méthode
La méthode Cluster_DoQuery a été écrite dès l’origine pour permettre la gestion de plus d’une table. Il
suffirait de rajouter des Au cas ou pour chaque table qui utilise la table des clusters. Il faut, bien sûr, un
BLOB différent dans la table [Mots] pour chaque table qui utilise la technique des clusters. C’est nécessaire
parce qu’on ne peut pas avoir un cluster valide pour un ensemble sur deux tables à la fois. La base centrale
4D Partner a actuellement trois BLOBs stockés dans la table [Mots] pour trois tables principales différentes.
Notez que la méthode Cluster_ProcessWordFinds utilise un booléen pour déterminer la condition ET/OU.
Ce booléen est passé en paramètre à Cluster_ProcessWordFinds sous forme d’une équation
( $tQueryType= "AND" )
Si (Faux)
` Méthode : CLUSTER_DoQuery(ptr;text;text)
` Created by: Kent Wilbur
` Objet: Effectuer une recherche par cluster
Fin de si
` Déclaration des paramètres
C_POINTEUR($1;$pTable)
4 / 11
C_TEXTE($2;$tQueryText)
C_TEXTE($3;$tQueryType)
` Réaffectation pour plus de lisibilité
$pTable:=$1
$tQueryText:=$2
$tQueryType:=$3
CLUSTER_Text2Array ($tQueryText;->atQueryValues)
Au cas ou
: (Taille tableau(atQueryValues)=0) ` Rien à faire, rien à chercher
: ($pTable=(->[BlocsTextes])) ` Faire une recherche ET
Au cas ou
: ($tQueryType#"Contains") ` Faire une recherche ET/OU
CLUSTER_ProcessWordFinds ($pTable;->[Mots]EnsembleBlocTexte;
->atQueryValues;($tQueryType="And"))
Sinon `Méthode pour chercher par contenu en utilisant les clusters
CLUSTER_ProcessWordFinds ($pTable;->[Mots]EnsembleBlocTexte;->atQueryValues;Vrai)
` Faire une recherche ET
CHERCHER DANS SELECTION($pTable->;[BlocsTextes]Titre="@"+$tQueryText+"@";*)
` Chercher dans ce qui reste
CHERCHER DANS SELECTION($pTable->; | ;[BlocsTextes]ZoneTexte="@"+$tQueryText+"@")
Fin de cas
Fin de cas
` Fin méthode
La méthode Cluster_ProcessWordFinds gère le passage en sélection courante des enregistrements trouvés
dans les clusters. Dans le cas où aucun mot n’est valide, elle réduit cette sélection à la sélection vide. Si un
seul mot-clé est à traiter, au lieu de créer et manipuler des ensembles, elle modifie directement la sélection
courante.
Si (Faux)
` Method: CLUSTER_ProcessWordFinds(ptr;ptr;ptr;bool)
` Created by: Kent Wilbur
` Objet : Traite le tableau de mots à chercher
` $1 = pointeur de table
`$2 = pointeur sur le champ blob qui contient les booléens
` $3 = pointeur sur le tableau des mots à rechercher
` $4 = indicateur ET / OU
Vrai = ET
Fin de si
` Déclaration des paramètres
C_POINTEUR($1;$pTable)
C_POINTEUR($2;$pBLOBField)
C_POINTEUR($3;$pArray)
C_BOOLEEN($4;$fAndQuery)
` Déclaration des variables locales
C_ENTIER LONG($LSizeOfArray)
` Réaffectation pour plus de lisibilité
$pTable:=$1
$pBLOBField:=$2
$pArray:=$3
5 / 11
$fAndQuery:=$4
$LSizeOfArray:=Taille tableau($pArray->)
Au cas ou
: ($LSizeOfArray=0)
REDUIRE SELECTION($pTable->;0)
: ($LSizeOfArray=1)
CHERCHER([Mots];[Mots]Mot=$pArray->{1})
CLUSTER_LoadFromBLOB ($pTable;$pBLOBField)
Sinon
CHERCHER([Mots];[Mots]Mot=$pArray->{1})
CLUSTER_LoadFromBLOB ($pTable;$pBLOBField;"TempSet")
ENREGISTREMENT SUIVANT([Mots])
Boucle ($i;2;$LSizeOfArray)
CHERCHER([Mots];[Mots]Mot=$pArray->{$i})
CLUSTER_LoadFromBLOB ($pTable;$pBLOBField;"TempSet2")
Si ($fAndQuery)
INTERSECTION("TempSet";"TempSet2";"TempSet")
Sinon
REUNION("TempSet";"TempSet2";"TempSet")
Fin de si
ENREGISTREMENT SUIVANT([Mots])
Fin de boucle
UTILISER ENSEMBLE("TempSet")
EFFACER ENSEMBLE("TempSet")
EFFACER ENSEMBLE("TempSet2")
Fin de cas
` Fin méthode
La méthode Cluster_LoadFromBLOB charge le tableau booléen contenu dans le BLOB et soit le convertit
en un ensemble, soit passe simplement en sélection courante le contenu du BLOB sans créer un ensemble, ce
qui rend le code plus efficace lorsqu’une seule valeur est recherchée.
Si (Faux)
` Method: CLUSTER_LoadFromBLOB(ptr;ptr{;str})
` Created by: Kent Wilbur
` Objet: Charge le blob dans un ensemble
`$1 = pointeur de table
`$2 = pointeur sur le champ blob qui contient les booléens
`$3 = nom de l'ensemble, si longueur = 0, prendre la sélection courante
Fin de si
` Déclaration des paramètres
C_POINTEUR($1;$pTable)
C_POINTEUR($2;$pBLOBField)
C_ALPHA(31;$3;$tSetName)
` Déclaration des variables locales
TABLEAU BOOLEEN($afBoolean;0)
$pTable:=$1
$pBLOBField:=$2
6 / 11
` Réaffectation pour plus de lisibilité
$tSetName:=""
Si (Nombre de parametres>2)
$tSetName:=$3
Fin de si
BLOB VERS VARIABLE($pBLOBField->;$afBoolean) ` Lire le tableau booléen dans l'enregistrement
Si (Longueur($tSetName)=0) ` Nous ne créons pas un ensemble, mais une sélection courante
TABLEAU BOOLEEN($afBoolean;Taille tableau($afBoolean)+1)
CREER SELECTION SUR TABLEAU($pTable->;$afBoolean;"")
Sinon
CREER ENSEMBLE SUR TABLEAU($pTable->;$afBoolean;$tSetName)
Fin de si
` Fin méthode
Une interface Web
Il faut très peu de travail supplémentaire pour offrir les mêmes fonctionnalités de recherche via le web. En
fait, il suffit de deux méthodes et quelques pages HTML. La page par défaut du site, index.html, est un
simple formulaire, presque le même que le formulaire de recherche ci-dessus.
Pour charger cette page html, entrez http://127.0.0.1 dans la fenêtre de votre navigateur.
Dans la méthode Cluster_DoQuery ci-dessus, le type de recherche est déterminé par les mots ET, OU ou
Contient. Donc, les boutons radio du formulaire envoient le mot pour lequel chaque bouton radio est
sélectionné.
<input type="radio" name="tQueryType" value="And" checked> <strong> Recherche ET</strong><br>
<input type="radio" name="tQueryType" value="Or"> <strong> Recherche OU</strong><br>
<input type="radio" name="tQueryType" value="Contains"> <strong> Recherche par contenu</strong><br>
Le bouton d’envoi utilise 4DACTION et appelle la méthode WEB_ClusterQuery. Du fait du mécanisme
d’affectation des variables dans le 4D Web Server intégré, la seule chose à faire est de déclarer toutes les
variables passées à 4D via le Web dans une méthode COMPILER_WEB
7 / 11
C_TEXTE(tQueryText)
C_TEXTE(tQueryType)
C_ENTIER LONG(LRecordNumber)
C_TEXTE(tCGI)
tQueryText:=""
tQueryType:=""
LRecordNumber:=0
Une fois les variables définies dans la méthode COMPILER_WEB, 4D affecte automatiquement les
variables d’un formulaire HTML aux variables correspondantes de 4D. Les déclarations de
COMPILER_WEB sont absolument nécessaires pour assurer le fonctionnement des formulaires envoyés à
4D via le web.
La méthode WEB_ClusterQuery réplique les fonctionnalités de la méthode M_ClusterQuery utilisée dans
l’interface 4D.
Si (Faux)
` Method: WEB_ClusterQuery
` Created by: Kent Wilbur
` Objet: Montrer la recherche par cluster via le web
Fin de si
CLUSTER_DoQuery (->[BlocsTextes];tQueryText;tQueryType)
TABLEAU ENTIER LONG(aLRecordNumber;0)
TABLEAU TEXTE(atTitle;0)
SELECTION VERS TABLEAU([BlocsTextes];aLRecordNumber;[BlocsTextes]Titre;atTitle)
ENVOYER FICHIER HTML("DisplayRecords.shtml")
` Fin méthode
Il ya deux manières d’afficher des enregistrements dans une page web avec une boucle.
1) En utilisant les enregistrements eux-mêmes ou
2) en utilisant des tableaux.
Nous voulons que l’utilisateur puisse voir l’information du formulaire détaillé s’ils le souhaitent. Pour cela,
avec les enregistrements eux-mêmes, nous aurions besoin d’une clé unique pour chaque enregistrement
[BlocsTextes]. Mais, pour garder cette base aussi simple que possible, je n’ai pas créé de champ clé. C’est
pourquoi nous allons utiliser le numéro d’enregistrement et, de ce fait, il sera plus simple de boucler sur des
tableaux plutôt que sur les enregistrements eux-mêmes. Deux tableaux sont créés, l’un pour les numéros
d’enregistrement et l’autre pour les titres.
Note : Vous ne pouvez pas utiliser des tableaux locaux pour l’affichage sur le web.
Enfin, le fichier HTML est envoyé. Notez l’extension .shtml. Ceci force 4D à analyser la page pour
rechercher les balises 4dvar qui pourraient se trouver dans la page. Sans l’extension .shtml, la page est
considérée comme statique et pourrait ne pas être analysée.
8 / 11
Comme cette méthode est appelée directement par 4DACTION, il est nécessaire d’activer la propriété de la
méthode qui l’autorise à être appelée par 4DACTION. Pour protéger votre base des accès indésirables, toute
méthode non appelée par 4DACTION devrait avoir cette option de sécurité décochée. (C’est l’option par
défaut pour les nouvelles méthodes, mais il faut vérifier cette option pour les bases converties depuis des
versions antérieures à 4D 2003 qui ont probablement cette option activée. C’était une nécessité pour la
compatibilité descendante.)
La page DisplayRecords.shtml
C’est le seul élément complexe créé pour afficher correctement les données. C’est complexe parce que cela
doit non seulement afficher les données obtenues avec des liens pour montrer les enregistrements trouvés,
mais aussi gérer toutes les erreurs d’entrée des utilisateurs.
Note : Toutes les pages html fournies utilisent des feuilles de style css. Je ne vais pas exposer ici comment
on les utilise ni comment elles fonctionnent. C’est un sujet sur lequel il existe des livres entiers. Je dirai
seulement que les feuilles de style css sont utilisées dans les pages html pour donner à la page une apparence
générale en matière de couleurs, bordures et polices. Cette note technique ne traite que la part active du corps
des pages .shtml.
<body>
<div id="wrapper">
<!--#4dif (Size of array(atTitle)=0)-->
<p><!--#4dif (Length(tQueryText)=0)-->You must enter someting to search if you expect to find anything!
<!--#4delse--><!--#4dif (Size of array(atQueryValues)=0)-->Sorry the value entered contains only non-indexed words. Please
try another phrase.
<!--#4delse-->Sorry, no records were found matching your request. Please try different words.<!--#4dendif-->
<!--#4dendif--><br><br><br><br></p>
<!--#4delse-->
<!--#4DLoop atTitle-->
<h1>Title:<a href="<!--#4dvar tCGI-->/4daction/Web_ShowRecord?LRecordNumber=
<!--#4dvar aLRecordNumber{atTitle}-->"><!--#4DVAR atTitle{atTitle }--></a></h1>
<!--#4DEndLoop-->
<!--#4dendif-->
</div>
9 / 11
<a href="http://www.4d.com/" id="logo"><img src="/images/4dlogo.gif" width="39" height="53" alt="4D Logo" /></a>
</body>
Il y a trois conditions d’erreur. Premièrement, aucun critère de recherche n’a été entré ; deuxièmement, tous
les mots entrés figurent dans la liste des exclusions ; troisièmement, aucun enregistrement n’a été trouvé.
Quoique ces conditions soient mutuellement exclusives, il n’y a pas de balise « Au cas où » pour l’affichage
de données dans une page 4d.shtml. De ce fait, le code utilise des Si en cascade, tous basés sur le fait
qu’aucun enregistrement n’a été trouvé. Comme dans d’autres technologies .shtml, les balises 4d sont
incluses dans des commentaires html :
< !--quelque chose-> . Quoiqu’une balise puisse techniquement fonctionner sans le # devant la fonction 4d, il
est vivement recommandé de l’inclure systématiquement, pour qu’il soit correctement interprété par les
différents éditeurs en cas de modification des documents html.
Si des enregistrements sont trouvés, une balise #4dloop est exécutée. Cette balise < !--#4DLoop atTitle--> doit
utiliser un tableau avec au moins un élément ou le nom d’une table dont la sélection contient au moins un
enregistrement et elle se comporte comme une boucle
Boucle (atTitle ;1 ;Taille tableau (atTitle )).
Dans cette boucle, le titre est affiché et un lien <a href> est créé à la volée pour chaque enregistrement du
tableau.
< !--4dvar tCGI/4daction/Web_ShowRecord ?LRecordNumber=< !--#4dvar aLRecordNumber{atTitle}-->
Dans cette base, tCGI n’a pas de valeur, rendant le lien relatif à l’URL dans le navigateur.
Après analyse, cela pourrait produire quelque chose comme
/4daction/Web_ShowRecord ?LRecordNumber=5553.
Affichage détaillé via HTML
Lorsque l’utilisateur clique sur un lien affiché avec la page DisplayRecords.shtml, la méthode
WEB_ShowRecord est appelée. Une fois de plus, assurez-vous que l’option de sécurité 4DACTION est
activée dans toutes les bases que vous créez.
Si (Faux)
` Method: WEB_ShowRecord
` Created by: Kent Wilbur
` Objet: Affiche l'enregistrement cherché
Fin de si
ALLER A ENREGISTREMENT([BlocsTextes];LRecordNumber)
ENVOYER FICHIER HTML("ShowRecord.shtml")
Ceci trouve l’enregistrement et envoie la page ShowRecord.shtml, laquelle affiche simplement les
informations sur l’enregistrement courant en utilisant directement les champs de la table.
10 / 11
Résumé
Dans cette série de notes techniques, j’ai tenté de montrer à quel point l’implémentation de clusters peut
améliorer les requêtes dans votre base.
Nos tests sur la base exemple ont renvoyé des résultats en un temps de l’ordre de la milliseconde. Avec
requête à un seul mot-clé via une page web, les résultats étaient renvoyés avant que j’aie le temps de bouger
ma souris après le clic sur le bouton de recherche. C’est rapide !
En y réfléchissant, vous pouvez adapter les clusters pratiquement à n’importe quoi. Ce ne sont pas
obligatoirement des champs textes. N’importe quelle recherche multicritères fréquente est candidate à
l’utilisation de clusters. Si vous avez des recherches fréquentes qui prennent plusieurs minutes, pensez aux
clusters. Imaginez le passage des minutes aux millisecondes.
Lorsqu’on parle d’optimisation pour une base, on répond compilation. C’est généralement une bonne
réponse, à moins que la base puisse utiliser les clusters. Rien n’améliore les performances comme le bon
usage des clusters. (Mais, vous pouvez bien sûr améliorer la création et la maintenance des clusters grâce à la
compilation.)
Maintenant que vous les connaissez, si vous en implémentiez dans votre propre base vous pourriez savourer
les félicitations de vos utilisateurs finaux pour une si notable accélération.
11 / 11

Documents pareils