La réflexion de Pollux disant que les adresses étaient forcément
connues par le compilateur à compile-time me titillait sous la douche
ce matin, et je suis arrivé à ça :
---------------8>--------------------------
template <class E>
struct Any
{
static const E obj;
static const Any<E>& obj_ref;
static const int offset;
E& exact() { return *(E*)((char*)(this) - offset); }
// For testing purposes, makes the address of Any<E> different than
// E's address.
int i;
};
// Nécessite un constructeur par défaut et stocke un objet par classe
// (pas par instance). Y'a-t-il moyen de contourner ça ?
template <class E>
const Any<E> obj = E();
template <class E>
const Any<E>& Any<E>::obj_ref = Any<E>::obj;
// Pourrait etre calculé directement dans exact().
template <class E>
const int Any<E>::offset =
(char*)(void*)(&Any<E>::obj_ref) - (char*)(void*)(&Any<E>::obj);
---------------8>--------------------------
L'idée est de prendre la différence d'adresse entre un objet de type
Exact et une référence dessus upcastée en Any<Exact>. Vu que ces
membres sont statiques et constants, le compilateur connaît leurs
adresses à la compilation et peut calculer l'offset à la compilation
également, et donc l'adresse du type exact.
Autre avantage, plus besoin de magouiller dans les constructeurs des
classes filles, la classe de base Any se débrouille toute seule !
Problème majeur pour l'instant, ça nécessite un moyen d'instancier un
objet exact dans Any, et ça stocke une instance de classe par
classe. Mais si on a des types qui peuvent être vides (genre les
images), ca reste cool.
J'en ai profité pour faire qq benchs sur un truc proche de ce qu'on a
dans olena (affectation de tous les points d'une image 10000x10000 en
passant par l'operateur [] de la classe de base Image), ca fait assez
peur avec 3.3 pour les hiérarchies multiples, mais 3.4 sera notre ami :
/*--------.
| g++-3.3 |
`--------*/
Avec héritage multiple (emulé pour le code en static_cast):
======================
Static_cast 1.42s
Pointeur sur exact 2.35s
Offset dynamique 2.55s
Offset statique 2.35s
Sans héritage multiple
======================
Static_cast 1.42s
Pointeur sur exact 1.45s
Offset dynamique 2.55s
Offset statique 1.42s
/*--------.
| g++-3.4 |
`--------*/
Avec héritage multiple (emulé pour le code en static_cast):
======================
Static_cast 1.40s
Pointeur sur exact 2.36s
Offset dynamique 2.57s
Offset statique 1.38s
Sans héritage multiple
======================
Static_cast 1.40s
Pointeur sur exact 1.42s
Offset dynamique 2.56s
Offset statique 1.41s
Bref, dans tous les cas la version offset dynamique est à la rue, et
le pointeur sur this est toujours à la rue aussi avec de l'héritage
multiple. Avec 3.4, l'offset statique s'en sort bien meme en héritage
multiple, alors qu'il se vautre comme les autres avec 3.3.
Enfin bon, tout ça est a prendre avec des pincettes, ca varie
beaucoup selon les compilos.
PS: ca ne marche pas avec 2.95, l'offset vaut toujours 0 !
Scénario:
---------
Développement d'un algorithme pour Vaucanson.
Développement d'un petit programme utilisant Vaucanson.
ie: un "main.cc" avec des inclusions des headers de la
bibliothèque.
Problème:
---------
Qu'est-ce que c'est long, la compilation !
Sans option de compilation, sur un 2.5Ghz :
~/work_local/vaucanson/vgrep % g++ -c -I ../prcs-toy/include vgrep.cc
7.84s user 0.28s system 98% cpu 8.275 total
Solution:
---------
1. Le programme est compilé une première fois (c'est long mais
c'est principalement à cause de l'analyse/instanciation du code
de Vaucanson). Cette compilation se decompose en deux etapes :
a. compilation d'une unité d'instanciation main.unit.o
(la même chose que le code de main.cc mais ses fonctions
sont envoyees dans un namespace poubelle).
b. compilation de main.o mais seulement avec les interfaces
de vaucanson.
c. liaison de main.o et main.unit.o.
2. Je travaille sur le code de mon algorithme (pas sur Vaucanson).
Faire b. puis c. tant qu'il n'y a pas de problème de liaison,
sinon faire a. b. c.
Benchmark:
----------
~/work_local/vaucanson/vgrep % make
g++ -I ../prcs-toy/include -c -o vgrep.unit.o vgrep.unit.cc
g++ -o vgrep vgrep.o vgrep.unit.o
make 8.20s user 0.37s system 96% cpu 8.885 total
... modification dans vgrep.cc ...
~/work_local/vaucanson/vgrep % make
g++ -c -I ../prcs-toy/include vgrep.cc -DINTERFACE_ONLY
g++ -o vgrep vgrep.o vgrep.unit.o
make 1.64s user 0.17s system 92% cpu 1.956 total
... repetition tant qu'on n'utilise pas de nouveaux types/algorithmes
de Vaucanson ...
Conclusion:
-----------
Soit un gain de 5 (qui a priori doit etre encore plus fort sur
des machines plus lentes).
Le Makefile qui suit est une ebauche grossiere
mais l'idee est la.
Je propose la distribution d'un tel Makefile(.am) dans la tarball
de Vaucanson.
--- Makefile --------------------------------------------------------------
---------------------------------------------------------------------------
CXX=g++
CXXFLAGS=
CPPFLAGS=-I ../prcs-toy/include
MAKE=make
GREP=grep
all: vgrep
vgrep.unit.cc:
@ $(GREP) '#include' vgrep.cc > vgrep.unit.cc
@ echo 'namespace __instanciator_code {' >> vgrep.unit.cc
@ $(GREP) -v '#include' vgrep.cc >> vgrep.unit.cc
@ echo '}' >> vgrep.unit.cc
vgrep.o: vgrep.cc
$(CXX) -c $(CPPFLAGS) $(CXXFLAGS) vgrep.cc -DINTERFACE_ONLY
vgrep: vgrep.o vgrep.unit.o
@ $(CXX) -o vgrep vgrep.o vgrep.unit.o &> /tmp/log || \
(grep 'ld returned 1 exit status' /tmp/log &> /dev/null && \
rm vgrep.unit.cc && \
$(MAKE))
@ cat /tmp/log