Facebook LinkedIn SourceForge Twitter RSS LastFM
logologo

Estimer la consommation mémoire des ressources GD

Geoffray Warnants|13/11/2010|5 commentaires

La librairie GD forme aujourd'hui avec PHP un couple apprécié des développeurs web. Elle offre en effet un accès d'une grande simplicité aux traitements d'images, ces opérations obscures qu'on sait truffées de formules mathématiques et autres transformations matricielles, et qu'on devine par conséquent probablement gourmandes en ressources matérielles. Mais sait-on exactement jusqu'à quel point ? Pour ma part non, jusqu'à aujourd'hui...

Le graphique ci-dessous représente la quantité de mémoire vive consommée par un script lors de la création d'une ressource GD en fonction des dimensions de l'image traitée (ou plutôt de sa définition pour être exact)

Mémoire allouée par GD selon la définition de l'image (GD bundled v2.0.34)

Comme on pouvait s'en douter, la mémoire requise est proportionnelle au nombre de couleurs ainsi qu'aux dimensions de l'image. On peut donc envisager qu'un fichier suffisamment imposant puisse saturer complètement la mémoire libre, celle-ci n'étant pas illimitée. Il en résultera alors un arrêt brutal du script causé par une erreur fatale (Fatal error: Allowed memory size of %d bytes exhausted (tried to allocate %d bytes)). Un sombre scénario qui, pour pouvoir être évité, doit d'abord être anticipé.

L'objectif de cette étude est donc d'estimer les dimensions maximales approximatives acceptables selon la configuration mémoire en vigueur. J'insiste sur le caractère approximatif car j'ai pu constater qu'à définition égale, la mémoire occupée par une ressource GD dépend aussi de ses proportions. Ainsi une image au format "portrait" (plus haute que large) risque de nécessiter d'avantage de mémoire que la même au format "paysage". Heureusement cette différence ne concerne de manière significative que des images étirées à l'extrême.

Voici donc les dimensions maximales estimées pour des images au format 4/3 :

memory_limit imagecreatetruecolor imagecreate
64M 13 Mpx (~4162x3123) 32 Mpx (~6532x4899)
128M 26 Mpx (~5889x4415) 65 Mpx (~9308x6983)
256M 53 Mpx (~8406x6305) 128 Mpx (~13064x9798)
512M 106 Mpx (~11887x8917) 262 Mpx (~18689x14019)
1G 201 Mpx (~16372x12277) 514 Mpx (~26179x19634)

Ces valeurs permettent alors d'instaurer des vérifications adaptées aux contraintes mémoire en vigueur. Par exemple :

<?php
ini_set('memory_limit', '64M');
$size = getimagesize($filename);
if ($size===false || $size[0] > 4000 || $size[1] > 3000) {
    throw new Exception('Image too large');
}
$rc = imagecreatetruecolor($filename);

Notons ici que le choix de la fonction getimagesize n'est pas anodin : elle permet d'obtenir les dimensions du fichier sans le charger entièrement en mémoire, ce qui n'est pas le cas par exemple de ses homologues imagesx et imagesy qui risquent de provoquer la saturation mémoire avant même d'avoir pu s'en prémunir...

Conclusion

La consommation mémoire des ressources GD n'est finalement pas si excessive. Les configurations standards permettent déjà de manipuler des images importantes sans soucis. Evidemment, je ne vous apprends rien en répétant que tout fichier externe doit être considéré comme une source de données potentiellement à risque, ce qui est d'autant plus vrai pour les images reçues par exemple au travers d'un formulaire d'upload, et qu'elles doivent donc toujours faire l'objet d'un contrôle minutieux avant d'être manipulées. Car une gestion correcte de la mémoire est avant tout du bon ressort du programmeur.

Réagir à cet article | Lire la suite >>>

jClockClock v1.0

Geoffray Warnants|07/10/2010|26 commentaires

jClockClock est un projet présenté dans le cadre de ma participation à un concours de développement web organisé par Développez.com, et pour lequel je fus élu l'heureux vainqueur. La ligne de conduite laissait libre cours à l'imagination et au choix des technologies pour concevoir une page web affichant l'heure de façon originale et novatrice.

L'idée que j'ai choisie consiste à représenter une heure digitale à l'aide des aiguilles d'une multitude de petites horloges analogiques. Il n'est pas à cacher que ce concept intéressant s'inspire de l'impressionnante oeuvre contemporaine TheClockClock réalisée par les designers suédois du studio Humans Since 1982, que j'ai simplement implémentée en Javascript avec jQuery. C'est donc d'abord aux créateurs de cette invention hors du commun que je remets à juste titre les éloges reçus quant à l'originalité du projet. Bravo également à tous les autres participants du concours pour leurs talentueux projets !

jClockClock

Réagir à cet article | Lire la suite >>>

Créer une liste circulaire FIFO associative en Javascript

Geoffray Warnants|23/09/2010|8 commentaires

Dans le cadre du développement d'un système de cache en Javascript, j'ai rencontré le besoin de disposer d'une structure de données associative organisée selon le modèle de la liste circulaire. Ce concept n'est si je ne m'abuse qu'un cas particulier de la file FIFO (queue), une structure déjà implémentée nativement dans le langage via les méthodes push et pop d'un simple objet Array au même titre d'ailleurs que la pile LIFO (stack) l'est via shift et unshift. Cependant, ces pratiques impliquent une gestion séquentielle de la collection, c'est à dire qu'il n'est possible d'accéder à un élément que via son index numérique et non via une clef associative.

J'ai donc créé une classe très rudimentaire qui fourni les fonctionnalités essentielles d'une file FIFO associative de taille fixe.

/**
 * GQueue
 * Fixed size FIFO queue class providing associative access. Can also be used as a Circular Buffer.
 * @author  Geoffray Warnants <http://www.geoffray.be>
 * @version 1.0.20100921
 */

/**
 * Creates an empty queue
 * @param {int} maxlength
 * @param {Boolean} circular If true, the queue as will be defined as a "Circular Buffer".
 * @constructor
 */
function GQueue(size,circular){
    this.data = {};
    this.circular = Boolean(circular);
    if (isNaN(this.maxlength=parseInt(size))) {
        this.maxlength=0;
    }
    this.length = 0;
}
GQueue.prototype = {
    /**
     * Adds new element to the end of the queue
     * @param {String}  k   Key
     * @param {Object}  v   Value
     * @return {int}    The new length, or -1 if the queue is full
     */
    push: function(k,v){
        if (this.circular) {
            this.data[k]=v;
            if (++this.length > this.maxlength) {
                this.pop();
            }
            return this.length;
        } else if (this.length < this.maxlength) {
            this.data[k]=v;
            return this.length++;
        } else {
            return -1;
        }
    },
    /**
     * Removes the last element of the queue
     * @return {Object}    The removed element, or null if none.
     */
    pop: function(){
        for (var i in this.data) {
            var value = this.data[i];
            delete this.data[i];
            this.length--;
            return value;
        }
        return null;
    },
    /**
     * Checks if a key exists in the queue.
     * @param {String}  k   Searched key
     * @return {Boolean}
     */
    has: function(k){
        return typeof(this.data[k])!="undefined";
    },
    /**
     * Searches an element for a given key
     * @param {String} k  Searched key
     * @return {Object}    The found element, or null if none.
     */
    find: function(k){
        return (typeof(this.data[k])!="undefined") ? this.data[k] : null;
    }
}

Comme le montre le constructeur, le second argument permet de définir si la structure doit se comporter comme une file FIFO classique, ce qui est le cas par défaut ou comme une file circulaire. La différence se constate dans le cas où l'on tente d'empiler un nouvel élément dans une file pleine : la file classique retournera une erreur tandis que la file circulaire permettra l'insertion tout en dépilant le plus ancien élément.

// créé une file FIFO circulaire de 3 éléments
var q = new GQueue(3,true);
for (var i=0; i<5; i++) {
    q.push("key"+i, "val"+i);
}

// accès associatif aux éléments via la clef
console.log(q.has("key1")); // affiche false
console.log(q.has("key2")); // affiche true
console.log(q.find("key3")); // affiche "val3"

Téléchargements

Réagir à cet article | Lire la suite >>>

La comparaison de nombres à virgule flottante en PHP

Geoffray Warnants|30/07/2010|6 commentaires

Malgré les considérables évolutions qu'a subi PHP, on ne peut nier qu'il est encore aujourd'hui trahi par de petites imperfections qui salissent son blason. Les détracteurs s'en donnent à coeur joie pour critiquer l'amateurisme du langage, les autres, certainement un peu fatalistes, préfèrent dire qu'il a le mérite d'être une source intarissable de surprises ;)

Dernière curiosité en date : La comparaison de 2 nombres décimaux n'est pas fiable. Ainsi :

var_dump(0.2+0.3 == 0.5);   // affiche true
var_dump(0.2+0.4 == 0.6);   // affiche false

Le second résultat est pour le moins inattendu ! Il se comprend néanmoins en regardant de plus près la valeur réelle de chacune des valeurs jusqu'à leurs infimes décimales.

printf('%.20f', 0.2);       // 0.20000000000000001110
printf('%.20f', 0.3);       // 0.29999999999999998890
printf('%.20f', 0.2+0.3);   // 0.50000000000000000000
printf('%.20f', 0.5);       // 0.50000000000000000000

printf('%.20f', 0.2);       // 0.20000000000000001110
printf('%.20f', 0.4);       // 0.40000000000000002220
printf('%.20f', 0.2+0.4);   // 0.60000000000000008882
printf('%.20f', 0.6);       // 0.59999999999999997780

"Honteux" crierons les détracteurs, "normal" diront simplement les autres. En effet, un avertissement dans la doc PHP nous met en garde face à ce comportement hasardeux bien connu et l'explique par le fait que la représentation interne de certains nombres décimaux n'est pas possible sans une infime perte de précision. Une remarque qui n'est pas sans raviver mes lointaines notions d'ASM et les prises de tête pour comprendre la représentation des décimaux en binaire.

Bref, ce problème d'approximation est inhérent à nos ordinateurs. On le rencontre d'ailleurs dans d'autres langages de bas niveau, notamment en C, langage avec lequel est écrit PHP.

Que faut-il faire ?

Comme indiqué dans la doc, comparer des nombres décimaux de manière classique est à proscrire. Pour ce genre d'opération qui nécessite une précision importante, PHP fourni un ensemble de fonctions spécifiques, dont bccomp qui permet de comparer des nombres de grande taille.

var_dump(bccomp(0.2+0.4, 0.6)==0); // affiche true

Une autre solution plus artisanale consiste à caster les nombres en chaînes de caractères avant d'effectuer la comparaison.

var_dump((string)(0.2+0.4)==(string)0.6); // affiche true

Cette solution semble tout aussi acceptable puisque lors de la conversion, PHP prend soin de retirer les éventuels zéros non significatifs qui pourraient poser problème en cas de comparaison textuelle.

var_dump((string)(0.2+0.4)==(string)000.60000); // affiche true aussi

La suite n'est plus qu'une histoire de préférence...

Réagir à cet article | Lire la suite >>>

Améliorer l'autocomplétion PHP de Notepad++

Geoffray Warnants|24/06/2010|23 commentaires

Même si le système d'autocomplétion de fonction proposé par l'éditeur Notepad++ n'est pas pleinement satisfaisant, il propose néanmoins une option que je trouve fort utile : l'autocomplétion de paramètres. Elle permet d'afficher instantanément en infobulle le prototype complet de la fonction native qu'on est en train d'utiliser.

Notepad++ auto-complete

Pour l'activer, il faut se rendre dans le menu [Paramètrage]/[Préférences]/[Sauvegarde/Auto-complétion] et cocher la case [Afficher paramètres pendant la saisie]

Malheureusement, en ce qui concerne PHP, la base de données utilisée par l'éditeur semble dater de Mathusalem. De nombreuses fonctions n'y sont pas répertoriées, et pire encore pour être induit en erreur, certains paramètres sont parfois manquants, les types et les valeurs de retour ne sont pas toujours corrects, la plupart des valeurs par défaut ne sont pas mentionnées, de même que les passages par référence.

Je me suis donc attardé à reconstruire une base de données aussi "up-to-date" que possible. Elle est téléchargeable ici (dernière mise à jour le 16/02/2011) et doit être extraite dans le répertoire Notepad++/plugins/APIs/

Réagir à cet article | Lire la suite >>>

<<< Articles plus récents | Articles plus anciens >>>

zend framework