mercredi 27 juillet 2011

Read

"Mais pourquoi", me demande mon collègue, "est-ce que mon programme fonctionne quand je le débugge, mais pas lorsque je l'exécute? On dirait qu'il attrape de mauvais paquets du réseau, et il se retrouve à lire n'importe quoi."

Arf, du C#. Tentons d'y voir clair.

D'habitude, j'ai absolument horreur de débugger un programme qui ne donne pas les mêmes résultats lorsqu'il tourne dans un débuggeur. Mais là, il m'a semblé que ça ne devait pas être trop difficile. Déjà, il fut facile de s'en assurer, ce n'était pas le débuggeur, mais les breakpoints. Sans breakpoints, le programme foirait, semblant effectivement lire des données pourries sur le réseau. Avec breakpoints, ça passait.

Je tentais de voir quels étaient les breakpoints "magiques", en les ajoutant et les retirant suivant la rigoureuse méthode de Monte Carlo (c'est à dire au pif). Finalement, une ligne attira mon attention. Quelque chose comme:


networkStream.Read(data, length);

Je changeais prestement la ligne pour afficher côte à côte "length" et la valeur de retour de la méthode. S'affiche:

67889 - 1280

Tête du collègue médusé. Eh oui. Même en C#, et comme en C, "Read" va lire et retourner ce qu'il trouve dans le tampon, et peut donc renvoyer moins que la taille demandée. Le breakpoint permettait simplement à l'ensemble du paquet d'arriver dans le tampon, et donc de faire fonctionner le programme (le contenu des données était ignoré). Mais lors d'une exécution normale, Read était appelé avant que tout le paquet ne soit arrivé. Lors de la deuxième lecture, c'était la fin du premier paquet qui passait, et qui était donc interprété en dépit du bon sens.

Je dirigeais mon coreligionnaire vers une méthode à nous, répondant au doux nom de ReadExactly, et implémentée avec une belle boucle qui accumulait les Read jusqu'à la taille désirée.

samedi 23 juillet 2011

Trop de choix


void cb::EntityManager::processEvents()
{
std::deque<std::shared_ptr<EntityEvent> > events;
m_eventQueue->popAll(events);
// version 1
std::for_each(events.begin(),
events.end(),
std::bind(&EntityEvent::accept, std::placeholders::_1, this));

// version 2
for(const std::shared_ptr<EntityEvent> & event : events)
{
event->accept(this);
}

// version 3
std::for_each(events.begin(),
events.end(),
[this](const std::shared_ptr<EntityEvent> & event){event->accept(this);});

}

Et je fais comment, moi, maintenant?

Autant j'adore les lambdas, autant c'est ici la solution la moins préférable: l'on a la déclaration explicite du contenu, contrairement à la solution basée sur std::bind, et l'on a l'overhead du la création de l'objet lambda, contrairement au range-based for.

Au final, je crois que je la solution du range-based for me plaît plus, car elle me semble vaguement plus efficace. Il faudra que je benchmarke un peu tout ça.

mercredi 20 juillet 2011

Coder efficacement avec Emacs - Partie 6 - Compilation

Une de mes fonctionnalités favorites d'Emacs, c'est la console qui affiche les résultats de compilation. Il est possible de cliquer (ou d'utiliser d'absconses commandes clavier) pour aller d'erreur en erreur à travers le source. C'est quand même bien plus pratique que d'avoir la compilation dans une console à côté, et d'aller chercher le fichier et la ligne.

La commande, c'est M-x compile. Tout bête, mais déjà trop lorsque l'on est un serial compileur comme moi. Un petit

(define-key global-map [f5] 'compile)
dans mon .emacs et voici la touche F5 affectée à la compilation. Tapons F5: la ligne de compilation par défaut apparaît. Entrée, et c'est lancé. La combinaison F5-Entrée est devenue chez moi un automatisme. L'on peut changer la ligne de compilation, qui sera maintenue tant que la session est ouverte.

La commande de compilation par défaut est make -k, mais il est possible de la changer pour un mode particulier. Si comme moi vous êtes un fanatique d'omake pour compiler vos projets C ou C++:

(add-hook 'c-mode-common-hook
(lambda ()
(set (make-local-variable 'compile-command) "omake -j 4 -R")))

Et pour changer la commande par défaut hors d'un mode particulier:

(setq compile-command "omake -j 4 -R")

Bon codage!

dimanche 17 juillet 2011

C++0x - std::unique_ptr et types partiels

Prenons un exemple très simple: l'idiome "pimpl" (dont le nom m'horripile, mais bon), encore appelé "pointeur opaque". Dans le .h, on forward déclare notre type interne, que l'on définit dans le .cpp. L'idée est de cacher l'implémentation, ce qui a également l'avantage de permettre une compilation plus rapide en évitant à l'interface l'inclusion d'une myriade d'en têtes pour les types privés.

Là, en bon dev C++11, l'on se dit, chic, je vais utiliser un std::unique_ptr. Voyons ce que cela donne. Ici, l'on a la classe Impl, forward déclarée, utilisée dans un std::unique_ptr, suivi d'un bout de code confirmant au compilo que l'on a bien l'intention d'instancier la classe Interface:


#include <memory>

class Impl;

class Interface
{
public:
Interface();

private:
std::unique_ptr<Impl> m_impl;
};

void create()
{
Interface i;
}

L'on est immédiatement couvert d'injures par le compilateur:

g++ -std=gnu++0x -c uniq.cpp
In file included from /usr/include/c++/4.6/memory:85:0,
from uniq.cpp:1:
/usr/include/c++/4.6/bits/unique_ptr.h: In member function ‘void std::default_delete<_Tp>::operator()(_Tp*) const [with _Tp = Impl]’:
/usr/include/c++/4.6/bits/unique_ptr.h:245:4: instantiated from ‘void std::unique_ptr<_Tp, _Dp>::reset(std::unique_ptr<_Tp, _Dp>::pointer) [with _Tp = Impl, _Dp = std::default_delete, std::unique_ptr<_Tp, _Dp>::pointer = Impl*]’
/usr/include/c++/4.6/bits/unique_ptr.h:169:23: instantiated from ‘std::unique_ptr<_Tp, _Dp>::~unique_ptr() [with _Tp = Impl, _Dp = std::default_delete]’
uniq.cpp:5:7: instantiated from here
/usr/include/c++/4.6/bits/unique_ptr.h:63:14: error: invalid application of ‘sizeof’ to incomplete type ‘Impl’


Rajoutons juste un destructeur:

#include <memory>

class Impl;

class Interface
{
public:
Interface();
~Interface();

private:
std::unique_ptr<Impl> m_impl;
};

void create()
{
Interface i;
}

Et g++ nous claque deux bises.

En effet, et d'après ce tableau, contrairement à std::shared_ptr, std::unique_ptr ne peut être détruit que si son type sous-jacent est complètement défini. Dans le premier exemple, le destructeur n'est pas défini, et g++ en définit donc un par défaut, inline. Il n'a donc pas accès au type Impl, ce qui cause l'erreur. Par contre, dans le deuxième exemple, l'on a décidé de fournir un destructeur quelque part ailleurs. Le compilateur n'a donc pas besoin de générer le code pour détruire notre pointeur. Celui-ci sera généré à la définition du destructeur, probablement dans le fichier source, et donc probablement après la définition complète d'Impl.

jeudi 14 juillet 2011

Design

Je ne suis pas convaincu par ma manière actuelle de mettre à jour mon graphe de scènes. Attendant entre chaque frame pour mettre à jour mes objets, je ne puis utiliser à fond les dernières fonctionnalités de threading d'OpenSceneGraph.

J'ai donc pensé à un système de queue d'événements, qui sont lus via un callback, et mettent à jour les structures qui sont lus par les nœuds dans leurs propres callbacks de mise à jour. Ces structures seraient de la forme (timestamp, position, direction), et permettraient au nœud de mettre à jour sa position à chaque image en extrapolant.

Tout est là dedans:

mardi 12 juillet 2011

Debian downgrade

Arf, petit bug! À la suite d'un crash dans la libdbd, je ne pouvais plus démarrer le bon vieux gnucash et faire mes comptes. Downgradons donc dans la joie et la bonne humeur.

Après quelques recherches, j'atterris sur la page snapshot de Debian, dans laquelle je puis retrouver mon paquet. Je décide de passer de la 0.8.3-1+s-1 à la 0.8.2-1-4.1, et me retrouve avec un lien vers la libdbd-pgsql 0.8.2-1-4.1. Un petit dpkg -i plus tard, je démarre GnuCash, et miracle, j'y suis!

Par contre, il va maintenant falloir faire attention à chaque fois que je mets à jour: ne pas laisser le système me remplacer ma précieuse libdbd avant que le bug soit fixé.

dimanche 10 juillet 2011

Debian, c'est comme attendre le bus...

On attend une version de gcc, et y'en a 3 qui arrivent en même temps :)

Maintenant qu'avec g++4.6, on a à peu près toutes les fonctionnalités de c++0x (c++11), j'attends maintenant avec impatience la mise à jour d'Iceweasel. Les web sockets m'intriguent, et bien qu'étant un peu allergique au Javascript, j'aimerais bien expérimenter avec des applications réellement réactives.

samedi 2 juillet 2011

C++0x - std::set vs std::unordered_set

Il aura tout de même fallu attendre C++0x (ou devrais-je commencer à dire C++2011?) pour voir enfin apparaître les tables de hachage dans la bibliothèque standard. Diverses extensions, ansi que Boost, proposaient leurs implémentations, mais ça fait toujours plaisir de voir les choses se standardiser.

Je dois cependant avouer que les tables de hachage m'ont souvent laissé un peu perplexe: leurs performances dépendent énormément du bon choix pour la fonction de hachage, ce qui rend fort difficile les prédictions. Je préfère généralement un bon arbre binaire, qui va certes nécessiter une allocation à chaque insertion et une recherche logarithmique, mais que j'ai l'impression de mieux comprendre.

Voyons donc un petit benchmark afin de se faire une idée. D'un côté, le bon vieux std::set, de l'autre, le std::unordered_set. L'on insère 1000 entiers, on mesure le temps, et l'on recommence.




Comme l'on pouvait s'y attendre, l'on voit que l'insertion dans un std::set augmente de manière logarithmique. L'on voit également que l'insertion dans un std::unordered_set augmente de manière linéaire, sauf au moment des rehash: une fois que la table est trop pleine, le conteneur réalloue une nouvelle table plus grande, et y hache de nouveau chaque élément.

Alors oui, le std::unordered_est est plus rapide que le std::set, sur un ensemble qui tendrait plutôt à défavoriser ce dernier: ayant utilisé des entiers consécutifs, le hachage est très efficace (c'est l'entier lui même), et il n'y a pas de collisions.

Cependant, si le std::unordered_set est plus efficace sur le long terme, ses performances instantanées peuvent tout d'un coup devenir absolument horribles si l'on doit re-hacher. Un programme qui nécessite de bonnes performances à chaque insertion, et pas seulement dans l'ensemble, devra l'éviter.

Une approche intéressante pour profiter des performances des tables de hachage sans payer un rehash couteux de temps en temps est d'amortir ce hachage: lorsque la table devient trop grande, l'on alloue une nouvelle table dans laquelle on met le nouvel élément. Chaque nouvelle insertion embarque avec elle quelques éléments de la vieille table, et chaque recherche vérifie les deux tables. Ainsi, au fur et à mesure, les éléments de la petite table sont déplacés dans la grande, et l'on peut se débarrasser de la petite, et recommencer le processus une fois que la grande devient à son tour trop pleine.

Voici le code du benchmark. Étrangement, la table de hachage a de meilleures performances à froid, et le std::set à chaud, ce qui est la raison pour laquelle je commence à insérer dans un set sans mesurer. Et je ne m'explique absolument pas le blip aux 3/4 de l'insertion dans le std::set, qui se reproduit à chaque exécution


#include <iostream>
#include <chrono>
#include <vector>
#include <set>
#include <unordered_set>

int main()
{
int interval = 1000;
int points = 1000000;

std::vector<std::chrono::microseconds> setTimes;
std::vector<std::chrono::microseconds> hashTimes;

setTimes.reserve(points / interval);
hashTimes.reserve(points / interval);

{
std::unordered_set<int> container;
auto base = std::chrono::system_clock::now();
for(int i = 0; i < points; ++i)
{
container.insert(i);
if(i % interval == 0)
{
hashTimes.push_back(std::chrono::system_clock::now() - base);
}
}
}

{
std::set<int> container;
auto base = std::chrono::system_clock::now();
for(int i = 0; i < points; ++i)
{
container.insert(i);
}
}
{
std::set<int> container;
auto base = std::chrono::system_clock::now();
for(int i = 0; i < points; ++i)
{
container.insert(i);
if(i % interval == 0)
{
setTimes.push_back(std::chrono::system_clock::now() - base);
}
}
}

auto it = setTimes.begin();
auto end = setTimes.end();
auto it2 = hashTimes.begin();
for(; it != end; ++it, ++it2)
{
std::cout << it->count() << "\t" << it2->count() << "\n";
}
std::cout.flush();
}