Je sens un bon gain en perf, mais j'ai un forum Skyomatic, pas de phpbbEnvoyé par Kioob
Je sens un bon gain en perf, mais j'ai un forum Skyomatic, pas de phpbbEnvoyé par Kioob
Il me semble qu'un objet est libéré automatiquement a sa derniere utilisation...Envoyé par ermelir
Le but de tout developpeur OO est de devenir une référence.
Mon avatar est un ambigramme, les curieux peuvent le retourner ;-)
Aider <> Faire a la place de!!!
Une optimisation qui peut également apporter d'énorme gain de temps d'exéctution lors de nombreuses requetes de selection dans une base de donnée et la mise en cache des résultats dans un fichier local. Le gain n'est évidemment sensible que si les données sélectionnées évoluent peu au cours du temps. Cette astuce dans sa forme actuelle est toutefois à employer avec précaution car tant que le fichier cache n'aura pas été supprimer, cela masquera les données réellement présentes dans la base.
Dans mon cas, il s'agit d'une gallerie photo. Si elle est mise à jour toutes les semaines c'est énhooooorme .
Pour mesurer l'ecart de performences, j'ai utilisé le bench fourni par iubito et effectué 5000 itérations
Voici les résultats que j'ai obtenu
La différence est phénoménale dans mon cas, ca va entre 8 et 9 fois plus viteRequete simple sur deux tables simultanées
SELECT a.id, a.filename, a.title, a.author, a.description, a.added, b.folder FROM MySite_pictures a,MySite_albums b WHERE (a.idalbum=55 AND b.id=55) LIMIT 0,9
Durée: 11.16851 s, Vitesse: 448 requetes par seconde
Requete simple sur deux tables simultanées, cache active
SELECT a.id, a.filename, a.title, a.author, a.description, a.added, b.folder FROM MySite_pictures a,MySite_albums b WHERE (a.idalbum=55 AND b.id=55) LIMIT 0,9
Durée: 1.48137 s, Vitesse: 3375 requetes par seconde
Requete AVEC JOIN
SELECT a.id, a.filename, a.title, a.author, a.description, a.added, b.folder FROM MySite_pictures a, MySite_albums b JOIN MySite_albums ON a.idalbum=b.id WHERE a.idalbum=55 LIMIT 0,9
Durée: 13.16669 s, Vitesse: 380 requetes par seconde
Requete AVEC JOIN et cache
SELECT a.id, a.filename, a.title, a.author, a.description, a.added, b.folder FROM MySite_pictures a, MySite_albums b JOIN MySite_albums ON a.idalbum=b.id WHERE a.idalbum=55 LIMIT 0,9
Durée: 1.30094 s, Vitesse: 3843 requetes par seconde
Voici le code que j'ai utilisé, pour ceux que ca interesse. Il n'a subit aucune optimisation particulière; il devrait donc être encore possible d'améliorer le résultat.
Code de Test
(Il y a la même chose avec la deuxieme requête)
Code : Sélectionner tout - Visualiser dans une fenêtre à part
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21 echo '<h2>Requete simple sur deux tables simultanées</h2>'; $SQL = "SELECT a.id, a.filename, a.title, a.author, a.description, a.added, b.folder FROM $Table_Pictures a,$Table_Albums b WHERE (a.idalbum=$Album_ID AND b.id=$Album_ID) LIMIT $FirstPic,$galPicturesPerPage"; echo $SQL.'<br>'; start(); for ($i=0; $i<5000; $i++){ $myPictures = $myDB->Query($SQL); }; stop(); echo resultat(5000, 'requetes'); echo '<h2>Requete simple sur deux tables simultanées, cache active</h2>'; $SQL = "SELECT a.id, a.filename, a.title, a.author, a.description, a.added, b.folder FROM $Table_Pictures a,$Table_Albums b WHERE (a.idalbum=$Album_ID AND b.id=$Album_ID) LIMIT $FirstPic,$galPicturesPerPage"; echo $SQL.'<br>'; start(); for ($i=0; $i<5000; $i++){ $myPictures = $myDB->Query($SQL, true); }; stop(); echo resultat(5000, 'requetes');
La suite, le code de ma classe qui effectue la requeteET enfin, les deux procedures que j'ai écrite pour stocker mes array dans des fichiers et les recharger par après
Code : Sélectionner tout - Visualiser dans une fenêtre à part
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47 /********************************************************************** * Generic SQL query. *********************************************************************** * @param $query * @retrun : **********************************************************************/ function Query($query, $Cache = false){ //Create the name of the file wich could contain the values $filename = $_SERVER['DOCUMENT_ROOT'].'/cache/'.md5($query).'.txt'; if (!(strpos($query, 'SELECT') === false) && ($Cache == true) && file_exists($filename) && is_readable($filename)){ $result = array(); $result = $this->LoadArrayFromFile($result, $filename); return $result; } else { //If there's no existing connection to the server, //let's try to create one. if (!$this->dbConnected){ $this->Connect();}; //If there's a connection to the server and if we can acces the //database we can execute our query if ($this->dbConnected && $this->OpenDataBase()){ //Construct the query $myQuery = $query; $this->LastQuery = $myQuery; if ($res = mysql_query($myQuery)){ $result = array(); $i = 0; while ($data = mysql_fetch_row($res)){ $j = 0; foreach ($data as $item){ $result[$i][mysql_field_name($res,$j)] = $data[$j]; $j++; } $i++; }; if (!(strpos($query, 'SELECT') === false) && ($Cache = true)){ //Store the result in the file for the next time $this->SaveArrayToFile($result, $filename); }; return $result; } else { $this->errorMessage = '<i>'.$myQuery.'</i><br/>'.mysql_error(); $this->errorNumber = mysql_errno(); if ($this->Debug){ $this->GetLastError();}; }; }; }; }
En modifiant ces deux dernières fonctions pour sérializer/desérialiser le tableau, on perd un peu en performance mais on gagne en souplesse
Code : Sélectionner tout - Visualiser dans une fenêtre à part
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50 /********************************************************************** * Save an array in a text file. An implicit conversion from each item * in the array to string must be possible otherwise it shouldn't work. * The array may contain arrays but those cannot. *********************************************************************** * @param &$array : pointer to the array to store * @param $filename : name of the file where the value must be saved * @retrun : / **********************************************************************/ function SaveArrayToFile(&$array, $filename){ $Header = implode(', ',array_keys($array[0]))."\r\n"; $Content = ''; foreach ($array as $item){ //Pour chaque photo $Content .= implode(', ',$item)."\r\n"; }; $handle = fopen($filename, "w+"); fwrite($handle, $Header.$Content); fclose($handle); } /********************************************************************** * Load an array form a text file. An implicit conversion from string * to the original type must be possible otherwise it shouldn't work. *********************************************************************** * @param $filename : name of the file where the value has been saved * @retrun : loaded array **********************************************************************/ function LoadArrayFromFile($filename){ $handle = fopen($filename, "r"); $Text = fread($handle, filesize($filename)); fclose($handle); $Content = explode("\r\n", $Text); //Recuperer les index des champs $i = 0; foreach(explode(", ", $Content[0]) as $item){ $Keys[$i] = $item; $i++; }; $Text_Count = count($Text); for($i=1; $i<$Text_Count; $i++){ $j = 0; foreach(explode(", ", $Content[$i]) as $item){ $array[$i-1][$Keys[$j]] = $item; $j++; }; }; } };
Code : Sélectionner tout - Visualiser dans une fenêtre à part
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27 /********************************************************************** * Save an array in a text file. *********************************************************************** * @param &$array : pointer to the array to store * @param $filename : name of the file where the value must be saved * @retrun : / **********************************************************************/ function SaveArrayToFile(&$array, $filename){ $handle = fopen($filename, "w+"); fwrite($handle, serialize($array)); //fwrite($handle, $Header.$Content); fclose($handle); } /********************************************************************** * Load an array form a text file. *********************************************************************** * @param $filename : name of the file where the value has been saved * @retrun : loadded array **********************************************************************/ function LoadArrayFromFile($filename){ $handle = fopen($filename, "r"); $Text = fread($handle, filesize($filename)); fclose($handle); $array = unserialize($Text); } };Requete simple sur deux tables simultanées
SELECT a.id, a.filename, a.title, a.author, a.description, a.added, b.folder FROM MySite_pictures a,MySite_albums b WHERE (a.idalbum=55 AND b.id=55) LIMIT 0,9
Durée: 11.7862 s, Vitesse: 424 requetes par seconde
Requete simple sur deux tables simultanées, cache active
SELECT a.id, a.filename, a.title, a.author, a.description, a.added, b.folder FROM MySite_pictures a,MySite_albums b WHERE (a.idalbum=55 AND b.id=55) LIMIT 0,9
Durée: 1.48182 s, Vitesse: 3374 requetes par seconde
Requete AVEC JOIN
SELECT a.id, a.filename, a.title, a.author, a.description, a.added, b.folder FROM MySite_pictures a, MySite_albums b JOIN MySite_albums ON a.idalbum=b.id WHERE a.idalbum=55 LIMIT 0,9
Durée: 13.24316 s, Vitesse: 378 requetes par seconde
Requete AVEC JOIN et cache
SELECT a.id, a.filename, a.title, a.author, a.description, a.added, b.folder FROM MySite_pictures a, MySite_albums b JOIN MySite_albums ON a.idalbum=b.id WHERE a.idalbum=55 LIMIT 0,9
Durée: 1.51247 s, Vitesse: 3306 requetes par seconde
SalutEnvoyé par iubito
vous êtes au courant que :
echo '<table>
<tr>
<td>Contenu du td</td>
</tr>
</table>';
marche aussi trés bien et à mon avis est bien plus lisible que votre concaténation de chaîne innutile à moins qu'une variable n'intervienne dans le chmilblique ...
Dans une fonction, est-ce qu'on est obligé de faire un unset à la fin de la fonction de toutes les valeurs créées dans la fonction si on veut que çà soit optimiser à mort OU est-ce que ces valeurs sont d'elles mêmes unset(er) dès le moment où l'appel à la fonction a été fait !
Autre question, personnellement je fais toutes mes requetes et la construction en html de la mise en forme de ces données en haut de page. JE rassemble tout dans une variable $toto. Je ferme ensuite ma connexion MySQL. Et je fais un echo $toto dans le body de la page qui est déjà préformatée avec un template dreamweaver. Est-ce c'est bon comme çà ?
Ensuite, après le </html> je fais un unset des variables que j'ai utilisé. Est-ce utile en toute fin de page ?
Merci encore pour vos précieux conseils
Salut
Non, appeler unset() est inutile en fin de fonction et en fin de script : PHP s'en charge.
Parfois (souvent), il ne faut pas penser à l'optimisation d'un script (réduire le temps d'exécution) mais à l'organisation du code (permettre une relecture facile).
Mes articles - Zend Certified Engineer (PHP + Zend Framework)
Ressources PHP - Ressources Zend Framework - Cours et tutoriels pour apprendre PHP - Forum PHP
Donc on a donc tout intérêt à mettre nos traitements dans des fonctions ?Envoyé par Yogui
Quant à la relecture facile, tu entends pour le développeur ou simplement pour PHP ?
Autre question bête :
si j'ai une structure de ce type:
requete1
traitement1
requete2
traitement2
requete3
traitement3
ai-je intérêt à fermer mysql après CHAQUE requête ? ou simplement de le faire à la fin de mon script !?!
J'entends pour le développeur car PHP ne "lit" pas, il "parse".
Tu as intérêt à mettre tes traitements répétés dans des fonctions, pas les autres.
Mes articles - Zend Certified Engineer (PHP + Zend Framework)
Ressources PHP - Ressources Zend Framework - Cours et tutoriels pour apprendre PHP - Forum PHP
Ca je l'ai fait depuis bien longtemps évidemment !Envoyé par Yogui
Je parlais simplement pour optimiser des pages très regardées que j'ai besoin d'optimiser à mort (bcp de hits) notamment pour libérer de la mémoire.
Aurais-tu une idée par rapport à l'exemple que j'ai donné sur mon post précédent par rapport aux fermetures mysql_close ?
Utiliser les fonctions mysql_ n'est pas une optimisation. Il y a au moins deux méthodes plus efficaces : les extensions mysqli et pdo/pdo_mysql.
Exemple avec PDO :
http://g-rossolini.developpez.com/tu...ite-dynamique/
Là, tu gagneras en performances. Il me semble que quelqu'un nous a promis un comparatif là-dessus mais peut-être me trompé-je.
[Edit] Beaucoup de hits simultanés ?
Est-ce que ton serveur a vraiment besoin d'être optimisé à fond ?
Si oui, peut-être serait-il intéressant de penser à Zend Optimizer.
Mes articles - Zend Certified Engineer (PHP + Zend Framework)
Ressources PHP - Ressources Zend Framework - Cours et tutoriels pour apprendre PHP - Forum PHP
c'est surtout qu'a terme les extention mysql,sybase,oci_8 ne seront plus misent a jour, au profit de PDOUtiliser les fonctions mysql_ n'est pas une optimisation. Il y a au moins deux méthodes plus efficaces : les extensions mysqli et pdo/pdo_mysql.
Exemple avec PDO :
http://g-rossolini.developpez.com/tu...ite-dynamique/
Là, tu gagneras en performances
pour ce qui est des perfs pdo est plus rapide si on a un grand nombre de requete (preparées)
Tiens justement !Envoyé par stephane eyskens
J'ai une table historique où j'effectue pour chaque page les trois requêtes suivantes :
Je n'affiche en revanche le contenu de cette table que sur une seule page dans le tableau de bord.
Code : Sélectionner tout - Visualiser dans une fenêtre à part
1
2
3
4
5
6
7
8
9
10
11
12
13 $insert="INSERT INTO `historique` (`idMembre`,`url`,`dateLecture`,`dateNombre`,`numero`,`titre`,`couleur`) VALUES ('$numeroConnexion','$retour','$dateLecture','$dateNombre','1','$titreHistorique','$couleur' )"; mysql_query($insert); $update = "UPDATE historique SET numero=numero+1 WHERE idmembre= '".$numeroConnexion."' "; mysql_query($update); $update="DELETE FROM historique WHERE idMembre = '".$numeroConnexion."' AND numero > 100 "; mysql_query($update);
J'ai des index sur trois champs de la table.
Puisque tu dis qu'il y a perte de performance à chaque insert car l'index doit être mis à jour (pourquoi au fait?), alors est-il judicieux dans mon cas d'utiliser les index ?
C'est pas parce que j'ai tort que vous avez raison.
Salut
Si je ne m'abuse, un index est utile principalement si l'on effectue de nombreuses recherches sur la table en question (par rapport aux insertions / modifications : cf. le message de Stéphane Eyskens). Dans le cas d'un historique, je doute que ce soit le cas...
Si l'index est mis à jour, c'est bien sûr parce qu'il recense tous les enregistrements de chaque champ mis à l'index. Si un tuple est ajouté, il faut mettre à jour l'index.
Tout était déjà dit, je ne sais pas si je suis + clair.
Mes articles - Zend Certified Engineer (PHP + Zend Framework)
Ressources PHP - Ressources Zend Framework - Cours et tutoriels pour apprendre PHP - Forum PHP
Donc à te lire, je vire les index de ma table historique. Vu qu'elle enregistre 100 pages vues par membre, ça peut très vite faire une table très importante.
Donc qu'est ce qui va être le plus long ?
Ne pas mettre à jour les index mais aller chercher dans la table tous les enregistrement pour un membre ou bien mettre à jour les index et aller chercher avec un index tous les enregistrements pour un membre.
Sachant qu'en effet l'index doit être mis à jours à toutes les pages alors que l'historique n'est appelé qu'occasionnellement.
C'est pas parce que j'ai tort que vous avez raison.
Je vais me contenter de citer ce qui est dit juste au-dessus :
Il faut uniquement mettre des index sur des grosses tables et qui sont requêtées à 85% VS mise à jour.
Mes articles - Zend Certified Engineer (PHP + Zend Framework)
Ressources PHP - Ressources Zend Framework - Cours et tutoriels pour apprendre PHP - Forum PHP
ok, cela étant j'ai du mal avec le jargon informatique :
requêtées à 85% VS mise à jour
C'est pas parce que j'ai tort que vous avez raison.
Cela signifie que les index ne sont utiles que si la proportion "requêtes de sélection" par rapport aux "requêtes d'insertion et de mise à jour" est de 85% par rapport à 15%.
Il faut donc que tu cherches davantage que tu ne modifies la table. Ce n'est pas ton cas.
Mes articles - Zend Certified Engineer (PHP + Zend Framework)
Ressources PHP - Ressources Zend Framework - Cours et tutoriels pour apprendre PHP - Forum PHP
Est-ce qu'on peut optimiser ce sujet ?
Il est trop lourd à charger dans ma RAM grise.
Most Valued Pas mvp
J'ai un résumé très simple : "Pour optimiser, il faut d'abord chercher ce qui est lent dans un programme, identifier les goulots d'étranglement". Ce résumé rends miraculeusement 90% de ce topic d'intérêt très faible : on connaît des solutions meilleures que d'autres mais dont l'application fait perdre en lisibilité contre des performances ne dépassant rarement les 1% d'amélioration. Parmi les conseils vraiment utiles, on peut citer Yogui en dernière page :
Et une URL :Parfois (souvent), il ne faut pas penser à l'optimisation d'un script (réduire le temps d'exécution) mais à l'organisation du code (permettre une relecture facile).
http://phplens.com/lens/php-book/opt...ugging-php.php
______
Fin du résumé : D (à compléter évidement, ce s'rait un travail intéressant, mais très long)
Il y avait des interventions intéressantes, mais c'est vrai que tout relire... Tous ce qui concerne l'optimisation des requêtes sur une base de donnée est quasiment utile, mais il faudrait certainement se référer au forum correspondant à la BDD utilisée. Il y en a beaucoup à prendre avec des pincettes. Genre, les calculs de temps d'execution en oubliant le temps de parsing. Et certaines solutions désignées comme "mauvaises" sont plus rapides à parser que les "bonnes" solutions. Ce qui revient à dire que le gain se fait uniquement dans des conditions de répétions, ou quand un cache d'opcode est activé. (Certains utilisent les optimisations, voir les conseillent dans leur signature, dans des cas qui sont hors de ces conditions) Cet exemple illustre bien la complexité de l'optimisation, quelque soit le langage d'ailleurs, et que l'on perd souvent beaucoup trop de temps à essayer d'optimiser pour des résultats ridicules voir insignifiants. (Et comme le dit implicitement Yogui à détruire la lisibilité du code) Gagner des microsecondes sur des scripts qui s'executent en des temps de l'ordre de la centaine de milliseconde ce n'est pas intéressant.
Il y a des études d'optimisation de script qui peuvent être efficaces avec les bons outils. On peut programmer ces outils soit même, en php : l'astuce est donnée dans une page assez inconnue du manuel php :
http://www.php.net/manual/fr/control...es.declare.php
Avec une poignée de lignes, on peut calculer le temps d'execution d'un script ligne par ligne : on peut mesurer avec une marge d'erreur raisonable le temps que la machine passe sur chaque ligne, ce qui permet d'identifier au premier coup d'oeil ce qui prends du temps dans un code php, quelles fonctions, quelles lignes, en rapport avec le temps total d'exécution, y compris le temps passé dans un include pour récupérer le temps de parsing. A partir de là, on peut identifier les segments de code lents et seulement alors tenter de les optimiser.
En termes plus pratiques un truc qui n'à pas été que peu abordé c'est la mise en cache des objets durant l'execution, c'est plutot pour php5.
zaventem en à un peu parlé sur un cas extrèmement particulier.
Bref, sa arrive souvent qu'un objet soit construit plusieurs fois pour une même représentation en base.
Un exemple simple serait le cas d'une liste de produit ave des marques.
Chaque objet produit pourrait avoir en son sein une référence vers une marque.
Hors il arrive souvent qu'une marque appartiennent à de multiples produits différent.
Ce qui, si l'on y fait pas attention, instanciera 20 objet de la marque toto.
en conséquence on obtient :
- Une rupture entre l'unicité de la marque en base, et l'unicité de la marque lors de sa transofmation en objet.
- Alourdit betement l'application qui execute N requetes identique pour le même objet.
Pour éviter ces pertes idiotes une bonne solution est de créer une petite classe de cache... et d'instancier ces objets au travers de méthodes statique. A bas le new Produit( $id );
En code sa donne :
Code méthode gourmande : Sélectionner tout - Visualiser dans une fenêtre à part
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34 class produit { public $nom; public $marque; public function __construct( $id ) { $sql = "SELECT * FROM ..."; $query .... $res.... $this->nom = $res["nom"]; $this->marque = new Marque( $res["idmarque"]); } } class Marque { public $id; public $nom; public function __construct( $id ) { $sql = "SELECT * FROM ..."; $query .... $res.... $this->id = $res["id"]; $this->nom = $res["nom"]; } }
Ou bien :
Code méthode plus légére : Sélectionner tout - Visualiser dans une fenêtre à part
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65 class produit { public $nom; public $marque; public function __construct( $id ) { $this->nom = NULL; $this->marque = NULL; } public static function lire( $id ) { $objet = Cache::Get( __CLASS__."lire".$id ); if( $objet != NULL ) return $objet; $objet = new Produit(); $sql = "SELECT * FROM ..."; $query .... $res.... $objet->nom = $res["nom"]; $objet->marque = Marque::lire( $res["idmarque"]); Cache::Put( $objet , __CLASS__."lire".$id ); return $objet; } } class Marque { public $id; public $nom; public function __construct( $id ) { $this->id = NULL; $this->nom = NULL; } public static function lire( $id ) { $objet = Cache::Get( __CLASS__."lire".$id ); if( $objet != NULL ) return $objet; $objet = new Marque(); $sql = "SELECT * FROM ..."; $query .... $res.... $objet->id = $res["id "]; $objet->nom = $res["nom"]; Cache::Put( $objet , __CLASS__."lire".$id ); return $objet; } }
Et le petit bout de code qui va bien pour sauvegarder/lire des objets en cache :
Je me suis permit de poster cela car j'ai vu assez souvent sur le forum des forumeurs qui faisait cela : new Produit( $id ); ce qui me laisse pensé que ce que je viens d'exposer pourrait les aider à ne plus executer de requetes inutiles.
Code : Sélectionner tout - Visualiser dans une fenêtre à part
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54 ////////////////////////////////////////////////////////// /// \class Cache /// Classe qui permet de mettre en mémoire des objets, puis de les récupérer ////////////////////////////////////////////////////////// class Cache { ////////////////////////////////////////////////////////// // propriétés ////////////////////////////////////////////////////////// private static $Cache = array(); ////////////////////////////////////////////////////////// /// constructeur ////////////////////////////////////////////////////////// private function __construct() { } ////////////////////////////////////////////////////////// /// Mets en un objet en cache /// l'uoid (Unique Object ID) est un identifiant unique ////////////////////////////////////////////////////////// public static function Put( $Object , $uoid ) { if($Object == NULL) return false; if( $uoid == NULL) return false; self::$Cache[$uoid] = $Object; return true; } ////////////////////////////////////////////////////////// /// Recherche un objet en cache /// l'uoid (Unique Object ID) est un identifiant unique ////////////////////////////////////////////////////////// public static function Get( $uoid ) { if( isset( self::$Cache[$uoid] ) ) return self::$Cache[$uoid]; return NULL; } ////////////////////////////////////////////////////////// /// destructeur ////////////////////////////////////////////////////////// public function __destruct() { } }
voilou,
bbye
Vous avez un bloqueur de publicités installé.
Le Club Developpez.com n'affiche que des publicités IT, discrètes et non intrusives.
Afin que nous puissions continuer à vous fournir gratuitement du contenu de qualité, merci de nous soutenir en désactivant votre bloqueur de publicités sur Developpez.com.
Partager