Gestion de la mémoire avec PHP

Aujourd’hui, Frédéric Hardy aka mageekguy a posté un article en ce qui concerne la gestion de la mémoire en php. Cet article se veut une réaction à celui-ci qui explique en regardant au sein du code source de php comment le langage gère la mémoire.

Si je reviens sur ces deux articles c’est que l’article original m’a un peu laissé sur ma faim et que le second m’a quand même choqué. Je n’ai peut être pas l’expertise ni l’aura de Frédéric Hardy, mais je n’arrive pas à me retrouver dans sa sentence : “idiot”.

Je ne reviendrais pas sur les arguments de ceux qui sont pour ou qui sont contre cet assassinat éditorial, mais je me concentrerai un peu sur la technique.Ou du moins ce que j’en connais et ce que ces articles m’ont appris.

Frédéric Hardy a le mérite, malgré ses propos agressifs de donner des exemples techniques et des conseils de bonnes pratique. Pour tous ceux qu’il cite, je les connaissais mais je croyais que c’était plus de la bidouille très geek de la part de quelqu’un qui est passionné par php que des pratiques très professionnelles.

En gros il y a deux grands points de “bonne pratique” qui doivent être respectée. La première concerne le code en lui même : utiliser, quand c’est souhaitable la fonction unset, comme par exemple, à la fin d’un foreach, ou bien pour libérer un tableau dont on n’a plus besoin (les tableaux php sont particulièrement lourds !).

D’ailleurs, c’est bien ça qui me fait dire que l’avis de Frédéric Hardy est trop totalitaire.
Comment savoir que l’usage de unset est souhaitable si on se fie simplement à l’instinct? On pourrait se dire que c’est clair qu’un tableau c’est plus lourd qu’un simple entier et donc que c’est là que ça devient utile. Oui mais non. En C une chaîne de caractères, c’est un tableau de caractères. Imaginons qu’on utilise une norme particulièrement gourmande en taille telle que UTF-32 pour représenter un caractère. Une chaîne UTF-32 prend donc autant de place qu’un tableau de int.
A l’opposé, en php faire un tableau de caractère ou une chaîne de caractère ce n’est pas pareil, même si l’opérateur crochet a été surchargé pour les strings. Ainsi même un simple array(‘a’); prendra plus de place que la chaîne de caractère “francoisdambrine.olympe.in”.

Autre exemple de code : les requêtes non bufferisées.
J’avais plus ou moins conscience de ce que c’était, mais je continuais quand même d’utiliser à tout va des fetchAll par pure facilité en m’assurant juste que ma requête ne prenait pas trop de résultat. En fait il semblerait que cette pratique sans être particulièrement mauvaise devrait être bien moins systématique.

Ensuite, le conseil de mageekguy : laisser aller ou bien utilisez un autre langage.

Par contre, des optimisations peuvent être amenées via l’environnement. Et là les deux le disent, c’est juste la forme qui change. C’est pourquoi je ne comprends pas que mageekguy soit si agressif : cache d’opcode, utilisation de la dernière version de php, tous les deux le disent.
Petite astuce apparemment utilisée en milieu professionnel et aussi par le geek que je suis : compilez vous même les versions de php. Cela permet de réduire grandement l’usage de mémoire.

Et en ce qui concerne l’Opcode, une bonne nouvelle a été annoncée sur la liste de diffusion de php il y a peu : ZendOptimiser+ va passer open source et est candidat pour être le moteur de cache par défaut de php. Si un tel moteur manque dans le coeur de php de manière évidente, ZO+ a l’avantage d’optimiser la compilation en opcode améliorant ainsi les dernières avancées de php 5.4 et 5.5.

 

PHP tableaux et data-structures

Cela fait plusieurs projets que je mène en PHP dans lesquels l’impact de mes choix de conteneur de données sont très importants. Basiquement, en php, en utilise les tableaux qui sont très intuitifs et très… puissants.  Pourtant pour des raisons d’occupation mémoire ou bien pour une utilisation très précise, eh bien ils sont gourmand. Petit tour d’horizon des tableaux et de leurs alternatives en php.

Les tableaux php : caisse omnipotents?

Conception

Soyons clairs : un tableau PHP, ce n’est pas un tableau C ou Java. Ce n’est pas une liste python, c’est… un tableau PHP. A la source, c’est une “ordered map”. Cela permet d’utiliser ces tableaux comme tableau, dictionnaire, liste, file, pile… Mais surtout, de manière sous-jacente cela signifie qu’en PHP tout ce qui sera rangé dans un conteneur de donnée (que ça soit un array ou autre chose), il y aura une association clé=>valeur.

Cela est extrêmement important car en PHP, cela signifie qu’avec un peu d’effort de conception, vous pouvez organiser vos tableaux pour qu’à l’usage tout soit en O(1). Vous sacrifiez un peu (beaucoup) de mémoire, mais ça va vite.
SI je prend le temps de dire cela, c’est qu’il arrive souvent qu’un développeur, habitué à des langages comme Java ou C construise son tableau comme un tableau indicé. Puis, comme il veut que ses données n’aient pas de duplicata, il fait une fonction du type :

[cc lang=”php”]
function insert($value,$collection){
if(array_search($value)!==false){
$array[] = $value;
}
}

[/cc]

Et ça c’est du O(n), donc le temps pour insérer un élément, au lieu d’être constant comme il l’est habituellement avec les tableaux php, sera croissant avec le nombre de données. Puisque nous sommes en php, rien ne nous interdisait pourtant de faire

[cc lang=”php”]
$array[$value]=isset($array[$value])?:true;
[/cc]

API

L’autre avantage des arrays PHP est l’API qui les accompagne. C’est cette API qui vous permettra d’utiliser les tableaux comme des piles/files (pop,push,shift), qui vous permettra aussi de voir les tableaux comme des ensembles (diff, merge, surcharge de l’opérateur + et -). Ces fonctions sont très nombreuses, et vous permettent d’économiser du temps de développement sur tout ce qui concerne les algorithmes de tri, de filtrage…

Les fonctions sur les arrays sont nommées de cette manière : array_nom_de_la_fonction. Et si la méthode a une variante qui accepte une callback, il suffira d’ajouter un “u” pour faire array_unom_de_la_fonction.

Les alternatives

Les alternatives que je présentes sont celles proposées par la SPL (Standard PHP Library). Je n’en présente volontairement que deux que j’ai utilisés récemment, mais un grand nombres de structures de données vous sont proposées. Vous aurez ainsi la possibilité d’utiliser une liste chaînée, une implémentation objet des tableaux, une pile et une file déjà codées. Mais place à deux structures que j’apprécie beaucoup.

Pour faire comme Java

Il faut faire plaisir aux fan de Java, il faut leur donner un tableau indicé à taille fixe qui ne se soucie pas des clés string des arrays.
C’est ainsi que fut créé SPLFixedArray. Son utilisation? comme un tableau java :
[cc lang=”php”]$array = new SPLFixedArray($size);[/cc] Contrairement à un tableau php habituel, il y aura d’emblée 5 cases de créées, et la taille sera de $size et non de 0. Un json_encode vous en assurera en imprimant :

{“0″:null,”1″:null,”2″:null,”3″:null,”4”:null}

Si, théoriquement un FixedArray est plus rapide (pas de hashmap) (5% sur la version 5.4, je ne sais pas pour la 5.5), son implémentation dans la 5.3 est légèrement plus lente qu’un array normal. Néanmoins, des progrès sont sans cesse faits et la conception en soit de la SPL évolue donc il faudra être patient. En fait la principale optimisation de la SPLFixedArray c’est l’utilisation mémoire. Ces derniers temps, j’ai migré un code qui se servait de matrice 10×10 de array vers SPLFixedArray. Auparavant, pour m’assurer du gain réel, j’avais été vérifier avec quelques benchmark le gain que je faisais et pour la mémoire, voici la différence :

  • Array : 225952 octets pour une matrice d’entiers 5×5
  • SPLFixedArray : 225632 octets pour une matrice d’entiers 5×5

gain : 320 octets pour 25 entiers stockés, ces entiers étant sur 64bits : nous faisons une économie de 320 octets pour 200 octets de données pures.

Cette tendance est réelle et confirmée par un gain de 1024 octets sur une matrice d’entiers 10×10.

SPLHeap

Se déclinant en MinHeap, MaxHeap et PriorityQueue, le tas est une structure de données que j’ai pu utiliser dans deux cas très précis, à savoir l’algorithme de Dijkstra et une insertion de données dans une collection de manière à ce que les nouvelles données ne cassent pas l’ordre désiré.

Ici il s’agit surtout d’éviter absolument des opérations de tri en O(nlog(n)) à chaque fois qu’on doit insérer une donnée. En effet même si les processeurs actuels sont rapides, c’est quand même assez long comme complexité. Consommer un “log(n)” à l’insertion permet d’éviter un tri complet à chaque fois. En plus cela permet de contrôler l’endroit où l’objet est inséré grâce à plusieurs paramètres pondérés.

Conclusion

Même si elle a certaines failles de conception la SPL vous offre des alternatives intéressantes pour ranger vos données de manière optimisée. Néanmoins les tableaux php restent sa plus grande force et permettent une optimisation temporelle des algorithmes qui est immense ! N’hésitez pas à réfléchir à la manière dont vous utilisez vos tableaux !

PHP 5.5 déprécie Mysql

Ca y est ! la version 5.5 de PHP qui est actuellement en version alpha jusque jeudi a marqué l’API Mysql qui commençait à dater comme dépréciée.

Cela signifie que désormais, à chaque appel de mysql_connect (ou de son équivalent pour les connexions persistantes) un WARNING vous sera envoyé vous conseillant de passer à Mysqli ou PDO. Si vous ne l’avez pas déjà fait, il n’est pas trop tard, et je vais vous rappeler (ou du moins vous présenter) quelques bonnes raisons de ce passage :

La sécurité

On ne vous le dira jamais assez, mysqli et PDO sont plus sécurisés. Pas simplement parce qu’ils proposent les requêtes préparées, qui sont naturellement “sécurisées” (à condition qu’on sache s’en servir) mais aussi parce que l’un comme l’autre corrigent les failles de mysql_ en ce qui concernent l’encodage, les connexions persistantes etc. Cela impliques quelques règles à respecter :

  • Il est obligatoire de donner la ressource de connexion à chaque fonction qu’on appelle. La POO simplifie cela puisque la méthode est liée au $this. Par contre le style procédural permis par mysqli_ vous oblige à écrire par exemple :[cc lang=”php”]$query = mysqli_query($connexion,’SELECT * FROM yourtable’);
    $result = mysqli_fetch_all($query);[/cc]
  • Sauf pour les requêtes dont vous savez qu’elles n’utilisent pas de paramètres, utilisez la fonction prepare. Attention néanmoins mysqli_ et PDO n’utilisent pas cette fonction de la même manière. PDO a deux grands avantages sur MySQLi en ce qui concerne les requêtes préparées : il permet des paramètres nommés et il permet de lier une valeur plutôt qu’une variable. Enfin pour PDO aller chercher le résultat d’une requête “normale” ou d’une requête préparée c’est la même chose donc les méthodes fetchAll et fetch existeront. Pour mysqli seul fetch existera et encore, il fonctionnera d’une autre manière que le fetch d’une requête normale.
    • Exemple de requête préparée avec PDO : [cc lang=”php”]$pdo = new PDO(‘yourDSN’,’user’,’pw’);
      $prep = $pdo->prepare(‘SELECT * FROM yourtable WHERE yourcol=:yourcol’);
      $prep->bindValue(‘yourcol’,"une chaîne avec des c’haractères embêt@nts’");
      $prep->execute();
      $result= $prep->fetchAll(PDO::FETCH_ASSOC);[/cc]
    • Exemple de requête préparée avec MySQLi :[cc lang=”php”]$mysqli = new mysqli(‘yourhost’,’user’,’pw’,’db’);
      $prep = $mysqli->prepare(‘SELECT * FROM yourtable WHERE yourcol=?’);
      $var = "une chaîne avec des c’haractères embêt@nts’",
      $prep->bindParam(‘s’,$var);
      $prep->bindResult($col1,$col2,$col3);//les résultats de chaque colonnes seront mis dans les variables associés
      $prep->execute();
      while($prep->fetch()){
      $result[] = array($col1,$col2,$col3);
      }[/cc]

     

Les fonctionnalités

L’ancienne API est ancienne et n’a pas été mise à jour avec Mysql 5 donc contrairement à PDO et mysqli elle ne pourra pas tout faire.
Au programme : requêtes multiples, support amélioré des transaction, support amélioré des conexions persistantes…

Migrer? mais comment?

En utilisant l’outil développé par Oracle.
En allant chercher quelques infos sur stackoverflow.

NoSQL le future?

Tant dis que j’apprenais avec @hsablonniere les méandres du javascript et la puissance de la JavaScript Object Notation aka JSON, quelques recherches sur le web m’ont rapidement ouvert au monde du NoSQL. Je ne prétends pas être un pro dans ce domaine dans lequel je vais seulement démarrer la semaine prochaine, néanmoins j’ai pu découvrir une multitudes de nouvelles méthodes pour gérer une base de données. Les bases de données SQL (structured Query Language) ne sont pas encore morte (ouf !) mais il faut avouer qu’elles ont leur limitation, tant en terme de performance, d’occupation mémoire que de conception.

Lorsque j’ai appris l’existence des bases de données NoSQL, je me disais “bah ça sert à rien”. Non pas parce que j’étais un troll mais parce que je voyais déjà arriver le “web sémantique” et que pour moi, j’en avais la certitude, sémantique = relation entre les données. En effet donner un “sens” à une phrase c’est la mettre en relation avec son contexte, avec la manière dont elle est créée etc. Tout comme identifier une image c’est peut être l’intercorreler avec une autre.

Si tout cela semble vrai au premier abord, soyons honnête, est-ce que la conception “MCD” avec ses relations entre entité correspond vraiment à cette idée qu’on met en relation deux “instances”? Une base “clef=>valeur” semble être une bonne idée non?

Pourtant avec l’acquis de mes cours sur la méthode Merise, sur l’élaboration de modèles conceptuels de données, avec mon utilisation courante de MySQL he bien, j’ai encore et toujours continué à faire du MySQL et donc du relationnel. Jusqu’au jour où un projet que j’avais commencé avec des amis et qui a été avorté depuis m’a posé un gros problème de conception puisqu’il nécessitait un système de versionnage (en fait cette fonctionnalité alliée à deux autres bonnes idées de mes amis créaient une sorte de “killer functionality”). En SQL, versionner c’est… lourd, long, pas pratique… et j’en passe ! Et j’ai aussi eu ce regret, en migrant mon blog, que les articles ne soient pas versionnés par wordpress. En plus quand on voit comment ce dernier range les choses, j’ai un peu peur !

Aujourd’hui, je suis encore et toujours avec mon projet de concours de programmation avec mes amis du club informatique ISEN. Période de disette oblige, je n’ai pas pu obtenir de serveur Windows Server et donc pas de IIS. J’ai tenté une installation Mono sur mon kimsufi mais comme je n’ai pas de nom de domaine alors que ce serveur est lier à trois autres noms il y a des conflits que je n’arrive pas à résoudre. J’ai demandé quelque aide sur serverfault, sans succès pour l’instant. Je commence donc sérieusement à revenir vers un système php. Mais comme j’aimerai offrir des replay de parties, un versionnage serait bien plus intéressant qu’un simple enregistrement des coups à jouer dans une arènes. Aussi ça serait bien plus rapide.

Actuellement, je me dirige donc vers la solution PHPCR présenté par David Bruchmann lors du PHP Tour 2012 de Nantes.

Les causes de la richesse de DateTime

Il y a peu, je digressais sur les forces de types complexes tels que DateTime en php sur le Timestamp unix. Une des raisons souvent invoquée (et que j’avais présentée comme pourtant peu importante) est que le timestamp est limité à 2038 en limite haute et à 1900 en limite basse (je rappelle : le timestamp est signé).

En php, DateTime est un type plus complexe et donc plus puissant. Mais certains me disaient “c’est inutile, DateTime ne fait qu’encapsuler un timestamp donc c’est plus lourd, plus lent pour juste des cas d’utilisation que les débutants n’utilisent pas”.

Premièrement c’est faux : mon expérience sur le site du zéro m’a montré que les débutants adoraient faire tout ce qui relève aujourd’hui des objet DatePeriod et DateInterval de manière assez crade. Par exemple, j’en trouve un la foi dernière qui veut retirer un mois à une date. Je lui répond : [cci lang=”php”]$taDate->add(new DateInterval(‘P1D’));[/cci]. Et de me faire allumer par un type qui dit qu’il suffit simplement de faire quelque chose type : [cci lang=”php”]strtotime(date("Y-",$time).(date(‘m’,$time)-1).date(‘-d’,$time));[/cci]. Heureusement que I.Jafrezic aka Zazou est venu à ma rescousse et a évangélisé pour DateTime.

Ensuite, d’un point de vue technique, DateTime n’est pas une simple encapsulation. Eh non, c’est bien plus complexe :

  • Le timestamp qu’elle contient est de type int64_t, c’est à dire que peu importe que votre architecture soit 32 ou 64 bits, vous stockez un entier sur 64 bits
  • D’autres champs tels que le jour, mois, année… sont aussi stockés. Le choix a été fait d’utiliser une nouvelle fois un int64_t, je ne sais pas pourquoi, mais sûrement pour éviter les pertes de temps en conversion.
  • Le support du fuseau horaire est ajouté

Voici, pour preuve de ce que j’avance, le code C de la structure à la base de dateTime :

[cc lang=”c”]struct timelib_time {
timelib_sll y, m, d; /* Year, Month, Day */
timelib_sll h, i, s; /* Hour, mInute, Second */
double f; /* Fraction */
int z; /* GMT offset in minutes */
char *tz_abbr; /* Timezone abbreviation (display only) */
timelib_tzinfo *tz_info; /* Timezone structure */
signed int dst; /* Flag if we were parsing a DST zone */
timelib_rel_time relative;

timelib_sll sse; /* Seconds since epoch */

unsigned int have_time, have_date, have_zone, have_relative, have_weeknr_day;

unsigned int sse_uptodate; /* !0 if the sse member is up to date with the date/time members */
unsigned int tim_uptodate; /* !0 if the date/time members are up to date with the sse member */
unsigned int is_localtime; /* 1 if the current struct represents localtime, 0 if it is in GMT */
unsigned int zone_type; /* 1 time offset,
* 3 TimeZone identifier,
* 2 TimeZone abbreviation */
}[/cc]