samedi 19 mars 2011

Les optimisations arithmétiques

À la question, lesquelles de ces fonctions sont optimisées par le compilateur ?

#define SIZE 16
int const Size = SIZE;

int withMacro(int x)
{
return x * SIZE;
}

int withConstant(int x)
{
return x * Size;
}

int withDecal(int x)
{
return x << 4;
}

int withMacro3(int x)
{
return x * SIZE * SIZE * SIZE;
}

int withConstant3(int x)
{
return x * Size * Size * Size;
}

int withDecal3(int x)
{
return x << 12;
}



La réponse est: Toutes ! En fait, les trois premières et les trois dernières produisent respectivement exactement le meme code un fois dessasemblées:

(gdb) disassemble withMacro(int)
Dump of assembler code for function _Z9withMacroi:
0x00000000 <+0>: push %ebp
0x00000001 <+1>: mov %esp,%ebp
0x00000003 <+3>: mov 0x8(%ebp),%eax
0x00000006 <+6>: pop %ebp
0x00000007 <+7>: shl $0x4,%eax
0x0000000a <+10>: ret
End of assembler dump.
(gdb) disassemble withConstant(int)
Dump of assembler code for function _Z12withConstanti:
0x00000010 <+0>: push %ebp
0x00000011 <+1>: mov %esp,%ebp
0x00000013 <+3>: mov 0x8(%ebp),%eax
0x00000016 <+6>: pop %ebp
0x00000017 <+7>: shl $0x4,%eax
0x0000001a <+10>: ret
End of assembler dump.
(gdb) disassemble withDecal(int)
Dump of assembler code for function _Z9withDecali:
0x00000020 <+0>: push %ebp
0x00000021 <+1>: mov %esp,%ebp
0x00000023 <+3>: mov 0x8(%ebp),%eax
0x00000026 <+6>: pop %ebp
0x00000027 <+7>: shl $0x4,%eax
0x0000002a <+10>: ret
End of assembler dump.
(gdb) quit

Et pout les versions 'au cube':

(gdb) disassemble withMacro3
Dump of assembler code for function _Z10withMacro3i:
0x00000030 <+0>: push %ebp
0x00000031 <+1>: mov %esp,%ebp
0x00000033 <+3>: mov 0x8(%ebp),%eax
0x00000036 <+6>: pop %ebp
0x00000037 <+7>: shl $0xc,%eax
0x0000003a <+10>: ret
End of assembler dump.
(gdb) disassemble withConstant3
Dump of assembler code for function _Z13withConstant3i:
0x00000040 <+0>: push %ebp
0x00000041 <+1>: mov %esp,%ebp
0x00000043 <+3>: mov 0x8(%ebp),%eax
0x00000046 <+6>: pop %ebp
0x00000047 <+7>: shl $0xc,%eax
0x0000004a <+10>: ret
End of assembler dump.
(gdb) disassemble withDecal3
Dump of assembler code for function _Z10withDecal3i:
0x00000050 <+0>: push %ebp
0x00000051 <+1>: mov %esp,%ebp
0x00000053 <+3>: mov 0x8(%ebp),%eax
0x00000056 <+6>: pop %ebp
0x00000057 <+7>: shl $0xc,%eax
0x0000005a <+10>: ret
End of assembler dump.
(gdb) quit

mercredi 22 septembre 2010

De la conception d'un server muti thread

Dernière ce titre pompeux se cache une prise de conscience. La vérité est parfois dure à admettre, mais en informatique, les choses sont souvent simple à mesurer, à comparer, en particulier lorsqu'il s'agit des performances.

J'ai récemment écrit un serveur http en C++, et c'est à la lecture de plusieurs articles, dont celui qui ma motivé à écrire cet article : http://pl.atyp.us/content/tech/servers.html, que je me suis rendu compte de la faute impardonnable que je faisais. Je pari que beaucoup de gens, je veux dire particulièrement les débutants, font aussi cette erreur grave. Les threads ne sont pas des êtres humain.

Pour comprendre mettons nous en situation. Prenons par exemple la fabrication d'une voiture. Si vous regardez une usine qui fabrique des voitures, vous vous apercevrez que sa construction est divisée en plusieurs étapes, que chaque étape doit se faire dans un ordre précis, et qu'au final, une voiture en sort à l'autre bout. On peut aisément comparer ce processus au traitement d'une requête par un serveur. Observons maintenant les travailleurs. Ceux ci sont affectés à une tâche bien précise, faisant un travail vital pour toute la chaîne, mais bien défini. Regardons pourquoi le concepteur de cette chaîne, sûrement un peu tayloriste, à choisit cette façon de faire. Les raisons sont simples, il adapte la chaîne de production à sa main d'oeuvre.

Les travailleurs sont affectés à une tâche pour minimiser les temps de déplacement, pour maximiser la concentration, pour que l'optimisation d'un geste maintes fois répété soit maximale, et pour d'autre raisons directement liées aux performances, au rendement.

Pour un serveur, on identifie un processus, que l'on divise ensuite en stage. Prenons par exemple l'écoute sur un port donné, et le traitement d'une requête. Vous ne voyez peut-être pas encore où je veux en venir, mais si vous deviez mettre des threads dans tout ça, vous devriez repenser encore: les threads ne sont pas humains. Ils ne sont pas déconcentrés, le temps de déplacement d'une tâche à une autre est nul (appel de fonction), le thread n'apprend rien, il est directement au maximum de ses performances. Il n'y a de fait plus aucun intérêt diviser le travail entre plusieurs threads. Si nous devions procéder ainsi, nous devrions perdre du temps pour que toute la chaîne soit synchronisée (des locks), utiliser des files d'attentes pour les tâches à faire, nous devrions passer des données entre plusieurs threads (context switches), la complexité augmente, les performances baissent. Non, dans le cas d'un serveur, je pense qu'il faut minimiser le nombre de thread (par exemple le nombre de coeur) et concevoir l'application comme s'il n'y avait aucun thread. Bien entendu, on reste capable d'augmenter facilement le nombre de thread, tout simplement en multipliant les usines. Si un seul thread peut écouter sur un ou plusieurs port et traiter toute la requête, deux pourront le faire sans problème, avec peu ou pas d'interaction entre les deux threads. Les performances seront multipliées par deux, la simplicité est conservée.

Bien entendu, il existe des cas où dans une même chaîne de traitement, plusieurs actions pourraient avantageusement être parallélisées. Ces cas sont rares, et si vraiment vous voulez y réfléchir, pensez à la complexité induite. On passe 10% du temps à écrire du code, 90% à le corriger.

lundi 19 juillet 2010

Un glouton web en sh :)

Ça faisait longtemps que l'idée me trottait dans la tête, surtout que j'avais déjà codé un certain nombre de mini scripts répondant parfaitement aux besoins du moment. Mais cette fois-ci, je me suis forcé a rendre la chose un peu plus générale. J'ai donc fait un jeu de quatre scripts permettant d'automatiser la recherche, le téléchargement et le nettoyage. Je vais détailler le travail de chacun des petits scripts, mais l'idée générale est la suivante : télécharger un ou plusieurs type de médias en masse en rapport avec un sujet quelconque.

1 - Le cherchage

Le premier script est preparedl.sh, il prepare un répertoire dédié aux futurs téléchargements et il cherche un maximum de liens sur la toile en rapport avec une recherche. On l'utilise de cette manière :
$ ./preparedl.sh "sujet" le_repertoire "mot en plus pour Google"

Il va commencer par une recherche Google, et rechercher dans le 40 premiers résultats tout les liens en rapport avec le sujet. Chaque fois qu'il trouve un autre liens, il l'empile pour plus tard. Ainsi, il va fouiller récursivement un grand nombre de page web, et va sauvegarder frénétiquement tout les liens vers les médias ou les pages en rapport avec le sujet.
Il est possible de faire par exemple :
$ ./preparedl.sh "Ben Harper" ben_harper "intitle:Index.Of"

Cette recherche a permis de trouver plus de 2600 fichier mp3 en 30 minutes. La recherche s'arrête d'elle-même lorsqu'il ne reste plus aucun lien a parcourir, mais il est possible de l'arrêter (Ctrl-C) et de reprendre (avec les résultats de la précédente recherche). Il est aussi possible de l'enrichir avec d'autre critères de recherches :
$ rm ben_harper/.download/search_result.urls
$ rm ben_harper/.download/pages.urls
$ ./preparedl.sh "Ben Harper" ben_harper "inurl:mp3 White Lies for dark times"

Dans tout les cas, aucune url n'est visitée plusieurs fois, que ce soit pour les pages ou les médias.

2 - Le downloadage

Ensuite, avant de lancer les téléchargements, vous pouvez voir ce qui a été trouvé au moyen du script stats.sh. Par exemple,
$ ./stats.sh ben_harper
Images: 0 (downloaded) / 0 (total)
Sounds: 0 (downloaded) / 2630 (total)
Videos: 0 (downloaded) / 0 (total)
Archives: 0 (downloaded) / 0 (total)
Torrents: 0 (downloaded) / 0 (total)

Pour lancer les téléchargement, vous pouvez utiliser le script dl.sh en lui donnant en paramètre les types de médias à télécharger parmi image audio video archive torrent all.
$ ./dl.sh ben_harper audio

Enfin, quand les téléchargements sont terminés, vous pouvez nettoyer les fichiers avec la commande clean.sh, elle supprime tout les fichiers ayant le même sha256. Elle fait un travail supplémentaire en supprimant les images trop petites ou ayant un format invalide. Vous voudrez surement jeter un coup d'oeil au script avant de supprimer violemment les images ayant une taille inférieure à 700x500 ;)

Voilà, les scripts ont besoin des programmes curl et wget pour fonctionner, ainsi que du programme sha256. Il sont disponibles ici http://hotgloupi.fr/glouton.tgz

lundi 22 février 2010

Regarder une video loin de chez soi...

Vous avez sûrement déjà été confronté à ce problème si vous possédez un serveur perso, sur une connection ADSL standard...

Des solutions ? Pleins ! La plus simple consiste a installer un serveur HTTP, comme lighttpd, et lire une jolie URL avec VLC par ou autre.
Mais ce post est là puisque je me suis confronté à la lenteur désespérante de ma connection. voici une méthode qui économise chaque bit de donnée en évitant un quelconque protocole de transfert de fichier :

Sur le serveur :
    $ tar cf - mon_film.avi | nc -l 49160


Ici, on ne compresse rien, puisque le fichier avi est déjà extrêmement compressé, mais au besoin rajoutez le flag 'z' aux commandes tar. On s'est évidemment assuré que le port 49160 d'écoute était disponible et accessible depuis l'exterieur.

Sur le client :
    $ nc example.com 49160 | tar -Ox | mplayer -idx -cache 8192 -


Le nom de domaine example.com est à remplacer par l'adresse ou le nom de votre serveur, et comme pour le serveur, si vous compressez le fichier en entrée, rajoutez ici aussi le flag 'z'.
Voilà, n'hésitez pas à m'envoyer vos méthodes :)

samedi 20 février 2010

De l'AJAX non intrusif avec PyJS

Quand on passe un site web en ajax, on doit faire face à au moins deux problèmes par rapport à un site normal. Tout d'abord, si les liens n'existent pas vraiment, c'est a dire au sens WEB1.0, le référencement ne sera pas optimal, et même si les moteurs de recherche analysent de plus en plus notre Javascript, il vaut mieux montrer à ces moteurs la page la plus simple à analyser. Le deuxième problème de taille, c'est l'éternel back button, auquel un prochain article sera dédié.

Voyons donc le cas de base. En général, il consite a récupérer un fragment de HTML et à l'integrer dans un div. Cette manipulation se fait en général de façon très simple avec la plupart des librairies AJAX. Avec PyJS, on écrirait quelquechose comme ça :
py.importModule("py.defer.xhr");

var d = py.defer.xhrGet({
url: 'mon_fragment_de_page.html',
prevent_cache: false,
onLoad: function (res) {
$('mainbox').attr('content', res);
},
onError: function (res) {
$('mainbox').attr('content', '<p>Error while loading content</p>' + res);
}
});
Ce bout de code, est particulièrement adapté à finir ses jours dans une fonction afin de rendre le tout plus flexible par exemple :
py.importModule('py.defer.xhr');

function ajaxLoad(url, id) {
var el = $(id);
if (py.isNone(el)) {
return ;
}
el.attr('content', 'Loading...');
return (py.defer.xhrGet({
url: url,
prevent_cache: false,
onLoad: function(res) {
el.attr('content', res);
},
onError: function(res) {
el.attr('content', '<p>Error while loading content from '+url+'</p>');
}
}));
}
Maintenant, il ne reste plus qu'a faire en sorte qu'un clic sur un lien ou un bouton appelle cette fonction avec les bons paramètres. Imaginons un bête lien de ce style :
<a href="mapage.html"> Ma Page </a>
Il faut faire en sorte que ce lien ne soit pas suivi lors d'un clic, mais plutôt qu'il déclenche l'appel de la fonction en question. L'idée est d'automatiser au maximum, et comme il est fréquent qu'il y ait plus qu'un seul lien... Par exemple, tout les liens du menu de gauche qui rafraîchissent le div central est un cas usuel. Il y a plein de manière de procéder, comme ajouter un attribut spécial sur les balises a, mais nous allons essayer de rester conforme w3c :).
Voici le type de liens que nous allons parser:
<a class="ajax-main" href="mapage.html"> Ma Page </a>
Le côté sympa, c'est que sur un navigateur qui ne supporte pas le javascript ce lien fonctionne, et les robots des navigateurs n'auront aucun mal à suivre ce lien. Pour nous, il nous suffit de parser le DOM lorsqu'il est prêt pour ajaxifier ces liens.
py.importModule("py.parser.html");
py.addOnLoad(function () {
var parser = new py.parser.HTMLParser();
parser.addHandler(new py.parser.HTMLElementHandler({
tag: 'a',
classes: ['ajax-main'],
onMatch: function(el) {
var href = el.attr("href");
el.attr("href", "#" + href);
el.connect("click", function(){ ajaxLoad(href); });
}
}));
parser.parse(py.body);
});
Et voilà ! Il est aussi possible de rappeler le parser quand la requête AJAX est terminée, et ça permet de mettre des liens à l'interieur du contenu central :)
Prochainement la gestion du backbutton !

vendredi 19 février 2010

Internet Explorer Element prototyping dans PyJS

Non, ceci n'est pas une nouvelle complainte sur les bugs d'IE, même si ça concerne un problème qui lui est spécifique. En effet, lors du développement de ma petite librairie PyJS, j'ai été confronté à ce problème.

Piqûre de rappel
Le prototypage des éléments du DOM se fait simplement sur la plupart des navigateurs, il suffit de modifier le prototype de l'objet Element
Element.prototype.myMethod = function () {
alert('useful method here');
};
Ainsi, tout les élément du DOM auront cette méthode :
var el = document.getElementById('some_id');
el.myMethod();
C'est assez génial, mais malheureusement, ça ne marche pas sous IE...

The IE way

La méthode pour modifier les élements du DOM sous Internet Explorer est assez particulière, et elle est limitée, nous verrons cela juste après. Voyons tout d'abord le Javascript :
// For IE
if (typeof Element === "undefined") {
Element = function(){};
}

Element.prototype.myMethod = function () {
alert('do something usefull here');
};
Evidemment, sous IE, le type Element n'existe pas. Maintenant, il faut lier les noeuds du document avec les méthodes de notre prototype. Nous allons avoir besoin d'un fichier spécial HTC. Par exemple :
<PUBLIC:COMPONENT>
<PUBLIC:METHOD NAME="myMethod" INTERNALNAME="_myMethod" />;
<script type="text/javascript">
var el = new Element;
_myMethod = el.myMethod;
</script>
</PUBLIC:COMPONENT>
Pour que ce fichier soit chargé, il faut utiliser une règle CSS :
<style>
* {
behavior: url(/lien/vers/le/fichier.htc);
}
</style>
Et là, magie, les noeuds du document courant possède tous la méthode myMethod.

Limitations de cette méthode

Encore une fois, IE va nous embêter. Les élements créés par la suite - que ce soit par la méthode crado innrtHTML ou en utilisant document.createElement - ne possèderont pas les méthodes précédemment liées. Il nous faut donc encore ajouter des hacks... Pour document.createElement, c'est simple, il nous suffit de remplacer la méthode native, par exemple comme ceci :
if (isIE) {
document._nativeCreateElement = document.createElement;

document.createElement = function(tag) {
var el = document._nativeCreateElement(tag);
el.myMethod = Element.prototype.myMethod;
return el;
};
}
Je vous laisse le soins de faire une boucle du meilleur cru pour assigner les méthodes du prototype Element. Pour ce qui est de la méthode innerHTML, deux solutions, soit l'éviter comme beaucoup le préconise, soit faire un parseur qui se charge de lier les méthodes aux éléments contenus. Pour les plus feignants d'entre vous, ma librairie contient un parseur basique ici.

MusiKissCool.Org ça arrive !

Voilà, j'ai craqué, j'ai finalement acheté les noms de domaine musikisscool.org et musikisscool.info. Ils serviront à présenter mon petit serveur de musique à travers le ninternet. Pour l'instant, je n'ai mis aucune source en téléchargement puisque je procède en ce moment à une refonte complète du serveur de média. En particulier, j'ajoute une notion de source, qui permettra de brancher diverse source de musiques sur un serveur. Je publierai dès que possible une description détaillé sur les portails de présentations, et biensûr, les sources de la nouvelle architecture :)