Greg's Devblog Par un développeur, pour les développeurs

6jan/116

Android 3.0 enfin présenté !

Ca fait longtemps qu'on l'attendait, à tel point qu'hier au boulot on se disant ne pas comprendre la stratégie de Google sur Android, avec un système pas du tout adapté aux tablettes et une relative opacité dans le développement (pour un projet OpenSource).

Et bien voilà, ce matin est arrivée une belle vidéo illustrant ce que sera Android 3.0 (Honeycomb), et je dois avouer que contrairement à ce que je pensais, je ne suis pas déçu, bien au contraire ! Les fameuses 4 touches d'Android (parfois 3), à savoir Back, Menu, Home, et Search ne seront effectivement plus obligatoires, car intégrées directement dans l'interface. L'iPad n'a qu'à se tenir !

Sans plus attendre, je vous laisse découvrir la vidéo :

Et vous, vous en pensez quoi ?

Via googlesystem.blogspot.com

18déc/100

Interrupted System Call

Hier j'essayais de terminer un petit player mp3 sur nos appareils eInk, mais au démarrage du mp3 ça quittait toujours brusquement. Le code récupéré des taiwanais utilise libmad, et pour faire simple, le principe est le suivi :

  • On ouvre un flux sur "/dev/dsp"
  • On le configure
  • Libmad décompresse le mp3 petit à petit, et à chaque morceau décompressé on le balance dans /dev/dsp
  • On referme le flux quand on a fini...

Parfois ça quittait dès le départ, et parfois on avait genre 1/4 de seconde du mp3 avant que ça ne quitte... On a rapidement repéré à quel endroit ça quittait : au moment d'écrire dans /dev/dsp (par blocs de 8192 octets), ça s'arrêtait très rapidement avec un message d'erreur : "Interrupted System Call".

Alors, à quoi correspond ce message ? En fait c'est tout simple : on a fait un appel système (ici, l'écriture dans /dev/dsp) qui a été interrompu par un signal... L'écriture dans /dev/dsp n'est en fait pas une opération atomique, donc on n'a aucune garantie qu'elle puisse se dérouler complètement en une seule fois.

La solution a mettre en place est assez basique : au lieu de quitter à la moindre interruption, on note combien d'octets ont déjà été écrits, on incrémente le pointeur d'autant, on décrémente la taille à écrire, et on relance le fwrite... Et ça marche !

Merci au site suivant pour la petite explication claire et précise : http://book.chinaunix.net/special/ebook/addisonWesley/APUE2/0201433079/ch10lev1sec5.html

Note au passage : c'est marrant de voir comment tout est flux sur Unix... Du coup, pour jouer un son, c'est aussi un simple flux dans lequel on envoie les données du son à jouer. On peut même faire un "cat toto.wav > /dev/dsp" et entendre son wav :p

18déc/100

Call Stack en C/C++

Ces derniers temps je me suis attaché au portage d'FBReader sur différentes plateformes, et comme c'est un code assez conséquent et où j'avais besoin de voir un peu quel appel de fonction arrivait d'où (autrement que par le "find reference" d'Eclipse), j'ai cherché rapidement s'il n'y avait pas un moyen en C d'afficher des stack trace. A ma grande surprise, c'est possible !

Voici donc une petite fonction qui peut se révéler utile lorsqu'on fait du debug :)

#include <execinfo.h>
#include <stdio.h>
#include <stdlib.h>

/* Obtain a backtrace and print it to stdout. */
void
print_trace (void)
{
  void *array[10];
  size_t size;
  char **strings;
  size_t i;

  size = backtrace (array, 10);
  strings = backtrace_symbols (array, size);

  printf ("Obtained %zd stack frames.\n", size);

  for (i = 0; i < size; i++)
     printf ("%s\n", strings[i]);

  free (strings);
}

Ca m'a bien dépanné, et même si je ne m'en suis pas tellement servi au final (juste une fois), je pense que ça fait parti des petits bouts de code toujours utiles à avoir sous le coude...

Pour plus de détails, je vous invite à vous rendre sur le site de GNU.

25nov/101

Premiers essais sur Cell

J'ai pu tâter un peu de la PS3 et du Cell ce matin en cours... Pas grand chose à en dire pour l'instant, on en est juste au "Hello World" sur les différents SPE. Mais pour l'instant je trouve ça sympa, et j'ai hâte de pouvoir résoudre de vrais problèmes avec pour voir ce qu'on peut en tirer niveau performances. Ca m'a l'air moins hardcore que la programmation sur GPU dans la mesure où ça revient juste à avoir 8 coeurs qui tournent de façon plus ou moins autonome, avec la possibilité de piocher dans une mémoire partagée si besoin (256ko de RAM, c'est pas énorme ^^).

Par contre, de ce que j'ai pu en voir, c'est que niveau debug ça risque d'être violent : si les données ne sont pas parfaitement alignées, ou qu'on transfert des paquets de tailles qui ne plaisent pas (genre 24 octets), alors ça va tout droit au SEGFAULT ou BUS ERROR... Pas super informatif, mais heureusement que le printf est là pour savoir ce qui se passe !

Taggé comme: , 1 commentaire
20nov/100

Réutiliser du code sans héritage multiple

L'héritage multiple : pas sans problèmes

En C++, on a l'héritage multiple, qui permet de rapidement factoriser du code, mais peut poser certains problèmes de "collisions" si plusieurs classes dont on hérite implémente une méthode du même nom, ou si plusieurs classes dont on hérite héritent elle-mêmes d'une même classe. Exemple simple : je créé un PointColore, qui hérite de Point3d et de Color. Que se passe-t-il si Point3d hérite d'une classe "Triplet" (pour x, y, et z), et Color aussi (mais pour r, g, et b) ?

Et histoire de donner un exemple dans l'autre sens, on pourrait avoir une classe VoitureAmphibie qui hérite de Voiture et de Bateau, et ces 2 classes qui héritent de "Transport" (qui contient alors des coordonnées x, y, et z, par exemple). Dans ce cas, on ne veut pas avoir les données de Transport en double, mais juste en 1 exemplaire...

Bref, ils existent plein de petits ajouts à la syntaxe pour palier à ça, mais ça introduit quand même une certaine ambiguïté, et donc un risque d'erreur (tout comme la surcharge à outrance).

Sans héritage multiple : la galère

Pour les exemples ci-dessus, comment procède-t-on si on n'a pas d'héritage multiple ? C'est notamment le souci en Java, et pour résoudre cela les interfaces ont été introduites... Sauf que c'est super, ça marche bien, mais avec les interfaces on ne factorise pas du tout le code... Alors oui, on peut créer un objet de la classe dont on voudrait faire l'héritage multiple, avoir un champs de cette classe dans notre classe, et puis "binder" toutes les méthodes, ça reste un peu chiant, redondant, et lourd pour pas grand chose...

Et le PHP dans tout ça ?

Il existe probablement plein de méthodes pour palier à ce problème, et je ne les connais pas forcément. Mais en lisant un peu par hasard des informations sur la prochaine version de PHP (suite à l'abandon de PHP6 !), je suis tombé sur une solution que je trouve à la fois simple et élégante : les traits.

Un bout de code vaut mieux que de longs discours, et comme je n'ai ni le temps, ni l'envie d'en faire un moi-même, je vais tout simplement repomper celui du wiki d'où j'ai tiré ces informations...

trait ezcReflectionReturnInfo {
   function getReturnType() { /*1*/ }
   function getReturnDescription() { /*2*/ }
 }

 class ezcReflectionMethod extends ReflectionMethod {
   use ezcReflectionReturnInfo;
   /* ... */
 }

 class ezcReflectionFunction extends ReflectionFunction {
   use ezcReflectionReturnInfo;
   /* ... */
 }

C'est tout simple, on peut voir les traits un peu à l'image des classes abstraites : on peut y mettre du code, mais on ne peut pas les instancier. Et en fait, l'idée est juste que ça revient à inclure le contenu des traits directement dans la classe. Et ça permet d'intégrer du code venant de différents endroits, en gardant un héritage simple, et donc en évitant le surcoût et/ou certaines des emmerdes liées à l'héritage multiple.

Alors certes, on va me dire qu'en pratique, on a les mêmes soucis qu'avec l'héritage multiple si on inclus plusieurs traits qui ont des méthodes ayant un même nom... Les équipes derrière PHP ont pensé à tout !

 class Talker {
   use A, B {
     B::smallTalk instead A;
     A::bigTalk instead B;
     B::bigTalk as talk;
   }
 }

Je ne prends pas la peine de détailler les classes et tout car seul le bout de code ci-dessus est intéressant. Mais en gros, les traits A et B contiennent des méthodes smallTalk et bigTalk, et on a donc un petit conflit si on inclus les 2 sans rien préciser. Du coup on peut préciser quelles méthodes utiliser (la méthode smallTalk issue de B, au détriment de celle issue de A), et on peut aussi renommer une méthode (la méthode bigTalk de B) en un autre nom si on veut pouvoir l'appeler tout de même...

Au final, on obtient un système souple et flexible, qui me fait penser à un mélange entre héritage mutiple et système de macros pour inclure du code à la compilation. D'après le wiki, le système des traits existent déjà dans d'autres langages, dont Scala, donc j'y jetterai un coup d'oeil à l'occasion. Mais bon, j'étais content de trouver une solution viable à tous ces problèmes, autres que les non-solutions proposées en Java...

20nov/100

Java : lenteurs inexpliquées ?

On aurait du utiliser un profiler. Voilà la conclusion à laquelle je suis arrivé après qu'on ait cherché à améliorer la vitesse et la réactivité de notre application au boulot (pour des appareils à encre électronique, donc en gros le même hardware que sur des smartphones).

Date.toString()

Sur certains écran, l'affichage prenait 6 secondes, alors que sur des écrans équivalents on était largement en-dessous de la seconde. On n'avait pas de profiler disponible sur l'appareil, donc on a commencé à mettre des logs un peu partout pour afficher les différents temps, et là, surprise : ce qui prenait autant de temps, c'est l'affichage des dates ! Un simple date.toString() peut ainsi prendre  plus d'1 seconde, parce que par défaut ça va aller charger tout et n'importe quoi pour trouver les locales et créer de quoi formatter la date comme il faut ! La solution, qui était déjà utilisée, a été de cacher les DateFormatter histoire qu'on ne se prenne pas le surcout à chaque fois et de bien convertir les dates en String en utilisant ce qui est en cache.

Requêtes à 2 à l'heure...

C'était déjà mieux, pas encore parfait. On avait encore des lenteurs assez bizarres sur des requêtes pourtant simples : de bêtes SELECT, UPDATE, ou INSERT pouvaient prendre 1 à 3 secondes, alors que d'autres prenaient à peine quelques millisecondes...

On a donc mis des logs autour de toutes les requêtes, en séparant les moindre lignes, pour essayer d'évaluer au mieux là où on passe trop de temps. Et surprise, l'exécution des requêtes est rapide, le problème n'est pas là ! En fait si on a une méthode qui ne fait que la requête, la ligne qui exécute la requête dans la méthode est rapide, alors que l'appel de la méthode en entier est long... Pas super logique :p

Bon bah c'est simple, il faut virer tout ce qui ne concerne pas directement la requête, à savoir juste des logger.trace qui ne sont même pas affichés, puisqu'on avait mis le mode de log à "info". Et là, gain de vitesse monstrueux !

Et oui, car afin d'avoir un maximum d'informations de debug, on a des logger.trace un peu partout dans le code des requêtes, qui permettent d'afficher toutes les valeurs injectées dans la base ou récupérée depuis celle-ci. Alors certes, rien n'est affiché, mais les arguments passés aux méthodes sont tout de même évalués, et parmi ceux-si on trouve... des dates ! Le retour du foireux Date.toString()...

On a été assez étonné car au final depuis le début on était persuadé d'avoir un problème de lenteur avec la base, alors que le problème était tout autre (et dans le fond venait de nous, et non de SQLite ^^). Ca explique pourquoi on ne trouvait pas trop de monde avec le même souci sur le net.

Régler le problème des logs

J'ai trouvé ce problème un peu con, en fait. C'est un souci qu'on n'aurait pas eu en C/C++ : les méthodes de log sont des macros, et quand on ne veut pas de log, la macro ne défini plus rien, et le log n'est tout simplement plus présent dans le code compilé.

Bon, à part commenter tous les logs (un peu chiant), que peut-on faire en Java pour ne pas avoir ce souci ? On n'a actuellement pas de préprocesseur, mais peut-être faudrait-il envisager d'en mettre un petit, au moins pour les logs... L'autre solution peut être de passer à un autre logger dont un collègue m'a parlé : au lieu de prendre juste un String, il reprend en fait la syntaxe du printf avec les "Date : %s..." et l'utilisation de la date comme argument. Et là, pour le coup, si le logger n'affiche rien, les arguments sont passés mais il n'y a virtuellement rien à évaluer (les dates resteront des Object tant que le logger ne les affiche pas), aucune concaténation de String à faire, c'est beaucoup plus rapide.

Alors certes, c'est mieux, mais conceptuellement ça reste du code qui pourrait être présent dans l'exécutable final, qui ne ferait rien, et qui ne serait pourtant pas 100% gratuit. Et j'ai un peu du mal à me dire que l'exécutable finale pourrait être légèrement (à peine, mais bon) plus lent que ce qu'il pourrait être juste parce qu'on a laissé trainé des debugs dedans...

Si vous avez des solutions alternatives, je suis preneur !

Moral ?

Ce qu'on a eu là comme souci illustre aussi quelques règles de programmation qu'on a parfois tendance à négliger :

  1. Pas d'optimisation à l'aveugle : quand on cherche à optimiser, il est important d'avoir des benchs pour savoir si l'optimisation est mieux ou pas que le code d'origine, et surtout savoir si on en train d'optimiser une partie pertinente du code. J'ai ainsi fait des optimisations sur le code d'affichage qui ont eu finalement un impact minime sur les performances globales, puisque 90% de la perte de temps était ailleurs. Et c'est comme ça qu'une fois sur PocketPC j'ai passé quelques heures à faire un code d'affichage beaucoup plus optimal pour me rendre compte que je n'avais rien gagné : GCC faisait aussi bien sur le code d'origine...
  2. Se remettre en question avant de douter des autres : ça fait plusieurs mois qu'on sait que les accès à la base sont lents, et depuis le début on a rejeté le faute sur SQLite... Pourtant les bonnes pratiques veulent qu'on vérifie que son propre code n'est pas la cause des lenteurs/bugs avant de dire que c'est dans une librairie qu'on utilise.
11nov/100

Android : remplacer AbsoluteLayout par RelativeLayout

Sur Android, Google avait prévu un AbsoluteLayout, dont le principe est relativement simple : on peut placer un View où on veut dessus, en pixels. Ce layout a cependant été déprécié pour des soucis de compatibilité des applications : si une application fait tous ses placements en pixels, le jour où on la met sur un écran de résolution différente, c'est pourri !

Sur les différents sites, quand on voit des gens demander ce qu'on peut utiliser pour remplacer l'AbsoluteLayout, pas mal de personnes crient en scandale en disant que si ce layout a été retiré c'est justement parce que placer des choses au pixel près c'est mal, et qu'il faut utiliser les autres layouts, qui eux seront bien indépendants de la résolution...

Certes, mais alors on fait quoi quand on a un vrai besoin ? Dans mon cas, on peut surligner du texte (dans une image), et j'ai besoin de pouvoir placer une icone à côté du passage surligné. Je connais les coordonnées à l'écran du surlignage, qui est fait en dur dans l'image, et je ne vois pas trop comment faire à part placer un bouton là où je le veux, au pixel près.

En fait, il existe une solution, et elle est toute simple : le RelativeLayout. J'en ai déjà parlé dans un précédent billet, le RelativeLayout est assez sympa car en fait on peut placer dedans des Views relativement à d'autres Views où aux limites du layout... En fait, par défaut un élément placé sur un RelativeLayout sera disposé relativement au coin supérieur gauche. Si on lui applique une marge, on peut donc réussir à le placer à n'importe quel endroit sur ce layout. Et voilà, en pratique le RelativeLayout peut se comportement exactement comme l'AbsoluteLayout.

Voici un bout de code non testé pour donner une idée de comment s'en servir :

int x = position X en pixels;
int y = position Y en pixels;

RelativeLayout l = new RelativeLayout(...);
RelativeLayout.LayoutParams params;

Button b = new Button(...);
params = new RelativeLayout.LayoutParams(WRAP_CONTENT, WRAP_CONTENT);
params.leftMargin = x;
params.topMargin = y;
l.addView(b, params);

Je vous laisse compléter les trous, ce n'est pas bien méchant. J'ai appliqué ça au boulot, et j'ai enfin obtenu le résultat que je voulais (j'ai placé mon RelativeLayout dans le FrameLayout qui contenait déjà les images, histoire que tout soit en superposition...)
A noter qu'il ne sert à rien de faire 1 layout par View (ici Button), on peut mettre tous ceux qu'on veut sur le même RelativeLayout.

Solution tirée de StackOverflow...

11nov/100

javaz : les sources !

J'ai décidé de me bouger le cul et de livrer les sources de javaz (compilateur Java pour Zam, le bytecode Caml). Il n'y a à l'heure actuelle pas de licence, pas de warning, pas d'instructions, rien :)

Vous pouvez compiler (nécessite d'avoir de quoi compiler du caml sur votre machine...) et tester, et un certain nombre de jeux de test sont déjà inclus et testable (avec "make tree", "make peano", etc...).

Je ferai probablement un petit billet sur les entrailles de la bête le jour où j'aurai le temps et si ça intéresse quelqu'un. Mais pour l'instant, c'est livré "as is".

Have fun !

Taggé comme: , , Aucun commentaire
9nov/101

CoffeeScript : syntax javascript compact

Ca fait un moment que je me dis que la façon usuelle de changer la valeur d'une variable en mettant un if est un peu moche à lire.

Le classique, suivi du "propre" :

if (condition) toto = truc;

ou

if (condition){
    toto = truc;
}

J'aurais voulu une syntaxe plus propre et lisible que ça, un peu de la même manière que parfois c'est plus clair de ne pas utiliser le if/then/else et de faire la chose suivante :

toto = condition ? truc : machin;

Bref, je pourrais très bien mettre "toto = condition ? truc : toto", mais je trouve ça laid de dupliquer le "toto"...

Et là, je tombe sur CoffeeScript, qui se propose d'écrire son code Javascript avec une autre syntaxe, et de générer le code Javascript correspondant après coup. Ca peut paraitre étrange, mais après tout, pourquoi pas ? En tout cas, dans leur syntaxe on trouve la construction suivante :

toto = truc if condition

Exactement ce que je voulais ! Clair, lisible, conci.

Je ne fais pas de Javascript en ce moment, mais l'approche me parait intéressante, donc je jetterai un oeil à ça un jour :) En tout cas, pour voir tout ça, c'est sur le site de CoffeeScript.

8nov/101

Surcharge en C#

Après avoir pas mal joué avec la surcharge en Java, je suis tombé par hasard ce matin sur un billet parlant de la surcharge en C#. J'ai hésité à lire, ça m'a paru trop simple, et pourtant ce que j'y ai lu m'a plutôt surpris.

Voici donc l'extrait de code en question :

using System;

class Parent
{
    public void Foo(String str)
    {
        Console.WriteLine("Parent.Foo");
    }
}
class Child : Parent
{
    public void Foo(Object str)
    {
        Console.WriteLine("Child.Foo");
    }
}
class Example
{
    static void Main()
    {
        Child child = new Child();
        child.Foo("test");
    }
}

Alors... Quelle est la méthode appelée ? Ca parait évident que la réponse est Parent.Foo, non ? En tout cas c'est ce que j'ai répondu, et j'ai perdu !

La réponse se trouve dans la doc (RTFM !) :

the set of candidates for a method invocation does not include methods marked override(Section 7.3), and methods in a base class are not candidates if any method in a derived class is applicable

Ce qui veut dire que dans notre exemple précédent, comme la classe fille Child expose la méthode Foo(Object) qui est applicable pour Foo("test"), alors on ne regarde pas du tout les méthodes disponibles dans la classe mère... On passe donc à côté d'une méthode plus précise et a priori plus intuitive.

Pourquoi ce comportement a-t-il été retenu ? D'après le site d'où j'ai trouvé cet exemple, le but est en fait d'assurer un comportement homogène dans le temps pour les sous-classes. Reprenons l'exemple précédent, mais en ignorant la méthode Foo(String) de Parent. On a donc une belle petite classe Child, dans laquelle on a implémenté une méthode qui est sensée pouvoir matcher avec n'importe quel objet...

Et là, un jour, sans rien nous demander, les personnes à l'origine de la Parent décident de la mettre à jour et de rajouter une méthode Foo(String). Et voilà, avec le comportement habitudel on se retrouve tout d'un coup avec des objets qui ne vont plus passer par la méthode Child.Foo, mais vont être capturés par la méthode Parent.Foo... Avec le choix fait dans .NET, au moins on est sûr de ne pas être dérangé.

Je reste assez partagé sur si c'est un choix pertinent ou pas. J'ai comme l'impression que ça risque de poser plus de problèmes que ça ne va en régler. Mais de toute façon, si j'ai bien appris une chose à propos de la surcharge, c'est bien qu'à partir du moment où on a une ambiguité, on va avoir des merdes :p

Source : Togaroga

7nov/100

Java : variable arity

Je ne sais plus où j'ai vu ça pour la première fois, mais en tout cas depuis Java 1.5 on peut faire des méthodes à arité variable, et j'ai trouvé la façon de faire plutôt pas mal.

Version C/C++

En C++, pour faire varié l'arité, on a en gros 2 façon de faire (en tout cas que j'utilise) :

void truc(int bidule, int blabla, float machin=0.0f, int ok = 0);

Ici, c'est particulier, car au final c'est juste qu'on donne des arguments par défaut. En Java, on peut obtenir le même résultat en surchargeant la méthode avec moins d'arguments, et en la faisant appeler l'autre méthode en mettant soi-même les valeurs par défaut.

int printf ( const char * format, ... );

Je prends volontairement printf, puisque c'est le meilleur exemple du genre, mais voilà, on peut aussi utiliser les "va_args". J'ai toujours trouvé ça un peu galère à utiliser, et la version Java me parait au final bien plus propre...

Version Java

public void myMethod(int arg0, int arg1, Object... objects){...}

La syntaxe est proche, sauf que cette fois-ci on précise le type des paramètres (en mettant Object on est super large, mais on met ce qu'on veut...). Ce qu'on récupère, au final, c'est juste un tableau "objects" contenant tous les Object passés en argument. Simple, efficace, on peut connaitre leur nombre, on peut boucler dessus avec une boucle foreach, et on peut aussi utiliser Arrays.asList(objects) si on veut en faire une liste.

Je m'en suis servi au boulot justement à un endroit où on voulait passer l'équivalent d'une liste d'éléments à utiliser dans une interface graphique. Je ne sais pas si niveau performance c'est bien (a priori pas forcément, car d'un côté on créé un array qu'il faut remplir, et de l'autre je converti l'array en liste), mais en tout cas au niveau code je pense qu'on a gagné en lisibilité. Ca évite de créer une liste, d'ajouter tous les éléments dedans, puis de passer cette liste en argument. Là on fait juste ça :

setKeyboards(KeyboardType.Alpha, KeyboardType.Num, KeyboardType.Symbols);

Bien ou pas ? Je dirais que ce genre d'ajout à un langage relève plus du sucre syntaxique qu'autre chose, puisque a priori il n'y a aucune nouvelle "fonctionnalité", juste une façon plus pratique d'écrire. Mais ça reste une façon simple et efficace d'enrichir un langage :)
(si ça se trouve dans 2 semaines je ferai un billet sur pourquoi c'est mal d'utiliser les méthodes à arité variable, mais pas grave ^^)

4nov/100

javaz : coming soon

Je fais un article rapide, la suite arrivera d'ici quelques jours ^^ Juste pour dire qu'hier avec Benoit (mon binome sur ce projet) on a rendu le premier jet de javaz, notre projet de compilateur Java pour zam (bytecode caml).

Le projet n'est pas encore très abouti, mais permet déjà de tester quelques trucs. On a de l'héritage, des appels de méthodes, des champs, après il manque des conneries genre les méthodes statiques, les génériques, les tableaux, et autres bétises.

Pour les tests, on a mis au point 2 exemples, un qui cherche des nombres premiers, et un exemple où on représente les entiers par des objets foireux : un objet de type Zero qui représente 0 (WOW !), et un objet de type Succ qui possède un objet (Zero ou Succ) et qui a pour valeur 1 + cet objet. Donc en gros pour représenter les calculs, c'est le bordel, et pour faire de simples additions ça coute super cher, mais au moins pour faire du bench sur les appels de méthodes et les accès aux champs, c'est marrant ^^

Je posterai tout ça avec quelques instructions d'utilisation demain. Mais pour résumer, en gros, niveau performances on tourne entre 3.5 et 10 fois plus lent que la JVM, ce qui est correct compte tenu du fait que le bytecode caml est exécuté dans une VM sans JIT, contrairement à la JVM :)

Projet intéressant, la suite demain !

Taggé comme: , , , Aucun commentaire
24oct/103

OcaIDE : eclipse se met au Caml

Je ne me voyais pas trop utiliser emacs pour le projet Caml de la fac, du coup mon binôme m'a proposé de tester OcaIDE, un plugin eclipse pour OCaml.

Ca fait maintenant quelques jours que je l'utilise, et je dirai que dans l'ensemble c'est plutôt pas mal. La compilation de notre projet ne marche pas, pour d'obscures raisons, mais je le fais dans la console et ça passe bien. Les fonctionnalités que j'utilise pour l'instant sont essentiellement :

  • L'auto-indentation, avec F3, qui rale quand on tape des conneries, donc ça aide à les voir ^^
  • L'analyse syntaxique, avec les endroits problématiques soulignés en rouge
  • Le top-level caml, où on peut tester quelques lignes vite fait, bien pratique
  • L'auto-complétion avec ctrl+espace, qui marche quand elle veut mais qui est plutôt pratique dans l'ensemble.

Au final, c'est une vraie solution viable comme alternative au couple emacs/tuareg, et ça pourrait séduire des neophytes (en matière de Caml). Il reste un défaut majeur, à savoir que c'est Eclipse, donc lourd et pas forcément super compacte au niveau interface, mais c'est toujours mieux que le bloc-note ou gedit !

21oct/100

Performances en Java

java

J'ai souvent des a priori sur Java et sur ce qu'il faut faire ou éviter. J'avais notamment en tête qu'il vaut mieux éviter d'utiliser de bons gros codes bien lourd avec de la récurrence à gogo, et en fait l'autre jour j'ai lu un test qui tend à prouver le contraire...

Quantifying Recursion on the Java Platform

Je ne vais pas faire un gros résumé de l'article puisqu'il est déjà court et très clair, mais en gros, j'en retiens 2 choses :

  • Un code récursif peut être plus rapide que son homologue itératif, en Java
  • Quoi qu'on fasse, il est important de savoir comment tourne le code derrière... Je pense notamment à l'utilisation du "foreach" (oui, je sais, c'est un simple "for" en Java), qui n'est pas forcément gratuite...

A propos de ce for spécial, même s'il rend le code particulièrement clair et lisible, ce n'est pas forcément gratuit pour autant. Cet article met ce phénomène en avant, car entre une boucle for "à la main" (avec un compteur et tout) et une boucle for "jolie", la consommation de mémoire est très différente, ainsi que le temps d'exécution.

Bon, ça ne va par contre pas dire qu'il vaut mieux éviter ce for, car en fait il peut y avoir des cas où ça sera l'inverse : ce qui se passe en pratique est que Java génère pour ces boucles des Iterators. Dans le cas d'un ArrayList, où on peut accéder à n'importe quel élément de la liste en O(1), ce n'est pas vraiment utile, voire même contre-productif. Par contre, si on utilise un autre type de liste, et là je pense tout particulièrement aux listes chainées, alors l'Iterator sera bien plus rapide et efficace (puisque l'accès à l'élément suivant est rapide) que si on utilise le "children.get(i)", qui lui va enclencher un parcours de la liste jusqu'à arriver au "i"ème élément à chaque fois...

J'ai pu constater ce genre de phénomène "à la con" à la fac l'année dernière, où les étudiants qui ne codent qu'en Java ont pris la fâcheuse tendance d'utiliser tout et n'importe quoi sans chercher à savoir en quoi ça consiste réellement. Quand ils ont voulu coder un Huffman en Java, ils ont eu un gain de l'ordre de 50x à un moment en changeant simplement le type de List utilisé !!!

Méfiance, donc :)

17oct/102

Evaluation non paresseuse en Java

A ma grande surprise, il est possible en Java d'utiliser les opérateurs binaires '&' et '|' sur des booléens, alors que j'étais persuadés qu'ils étaient limités aux opérations binaires sur les entiers...

En fait, Ces symboles font presque la même chose que '&&' et '||', mais sans évaluation paresseuse. Etudions ce que fait le code suivant :

if (checkFoo() && checkBar()){
	...
}

Ici, si checkFoo retour FALSE, il n'est pas utile d'évalué checkBar, puisqu'on sait par avance que le résultat du test dans le if sera FALSE. Par évaluation paresseuse, checkBar n'est donc pas appelé... Or, que se passe-t-il si dans checkBar on avait un effet de bord ? Car comme celui-ci n'est pas effectué, cela pourrait changer l'execution du programme.

Avec le code suivant, on passe outre l'évaluation paresseuse :

if (checkFoo() & checkBar()){
	...
}

Le résultat du test sera inchangé, mais par contre cette fois-ci checkBar sera appelée.

Je pense que dans l'ensemble il est rare d'avoir besoin de ce genre de subtilité, et j'utilise toujours l'évaluation paresseuse (qui, par définition, est sensé être plus rapide...) ; mais on ne sait jamais, ça pourrait servir un jour :)

A noter qu'on peut par contre trouver des cas où il est nécessaire d'avoir une évaluation paresseuse pour que le programme tourne correctement. Voici un en C/C++, et un autre en Java :

my_type *p = getThing();
if ((p != NULL) && p->bool_value){
	...
}
MyObj obj = getObject();
if ((obj != null) && obj.isAlive()){
	...
}

Dans les 2 cas, l'évaluation paresseuse va permettre de garantir que l'expression de droite ne sera évaluée que si la première est validée. Dans la version C, on a donc la garanti que le pointeur sera valide (ou en tout cas non null), et de même avec la référence en Java.