nargy | Salut les C++eurs!
Je cherche à faire un pool d'objets.
Cas particulier d'utilisation:
- Au démarrage, allocation jusqu'à 100K objets de plusieurs classes dont certaines de tailles identiques et d'autres de tailles différentes.
- Réutilisation de ces objets un nombre illimité de fois.
Après recherches d'imlémentations & d'algos, j'ai concocté un code composé de deux classes template:
- class Pool, contenant une pile chaînée dans un tableau. La pile est composée des éléments: soit libérés et chaînés, soit occupés et avec meta informations.
- class Poolable, à faire hériter par d'autres classes pour être mises en pool.
Un peu de code: Code :
- #include <stdio.h> // printf
- #include <stdlib.h> // malloc/free
- #include <sys/time.h> // gettimeofday
- // taille max d'un pool
- const unsigned int maxPoolElements=100000; // 100K
- // métadonnée sur l'allocation, kedal pour l'instant
- typedef void PoolAllocInfo;
- // une classe template de Pool, gérant la taille ``elementSize``
- template <size_t elementSize> class Pool
- {
- // Le pool est constitué d'une pile chaînée, template statique
- static class Pile
- {
- // la pile chaînée est elle-même composée d'éléments
- struct PileElement
- {
- // l'objet est mis en premier pour faciliter free()
- // rem: conséquences sur l'endianness?
- char c[elementSize]; // réserver de l'espace pour l'objet
- // métadonnées
- union
- {
- // soit l'élément est libre et pointe sur le prochain libre...
- PileElement* next;
- PoolAllocInfo* info; // ...soit il est occupé
- } meta;
- };
- PileElement* pile; // la pile (tableau avec alloc standard)
- PileElement* first; // le premier élément libre
- PileElement* last; // le dernier élément, pas encore utilisé
- public:
- Pile() // et hop! une nouvelle pile
- {
- // alloc standard
- pile=first=(PileElement*)malloc(sizeof(PileElement)*maxPoolElements);
- // boucle avec arithmétique pointeurs...
- PileElement* m=first+maxPoolElements-1;
- // ...où on initialise les métadonnées (meta.next)
- for(PileElement* c=first;c<m;)
- { PileElement* n=c+1; c->meta.next=n; c=n; }
- // cas à part: initialiser last et sa méta
- last=m;
- m->meta.next=0;
- }
- inline ~Pile() // et hop! une pile en moins
- { free(pile); }
- inline void* allocate() // là on alloue
- {
- if(first) // s'il reste de la place
- {
- // on dépile de la liste libre
- PileElement* e=first; // libre ne l'est plus...
- first=e->meta.next; // ...et le pochain est libre
- return e;
- } // un petit message qui rappelle le Basic sur Z80
- else { fprintf(stderr,"Missing memory\n" ); exit(1); }
- }
- inline void free(void* x) // Mémoire, par ordre de SM, je vous libère!
- {
- // on prie pour que ``x`` soit un pointeur retourné par allocate()
- PileElement* e=(PileElement*)x;
- // on empile dans la liste libre
- e->meta.next=first;
- first=e;
- }
- // les objets alloués sont fichés
- inline static const PoolAllocInfo* info(void* x)
- {
- PileElement* e=(PileElement*)x; // abracadabra!
- return e->meta.info;
- }
- } pile; // ça c'est la pile (static) pour la taille <elementSize>
- public: // service public
- inline static void* allocate() { return pile.allocate(); }
- inline static void free(void* x) { return pile.free(x); }
- inline static const PoolAllocInfo* info(void* x) { return pile.info(x); }
- };
- // dans la famille template <size_t elementSize>...
- template <size_t elementSize>
- // ...je veux la pile de Pool!
- typename Pool<elementSize>::Pile Pool<elementSize>::pile;
- // classe à hériter pour être mis en pool
- template <class Element> class Poolable // nom à la c...
- {
- public:
- inline static void* operator new (size_t s) throw() // new Element
- {
- if(s!=sizeof(Element)) // le throw, c'est pour le chien
- { fprintf(stderr,"Memory allocation size mismatch\n" ); exit(1); }
- return Pool<sizeof(Element)>::allocate();
- }
- inline static void operator delete (void* p) // delete Element
- { Pool<sizeof(Element)>::free(p); }
- inline const PoolAllocInfo* allocInfo() // info Element
- { Pool<sizeof(Element)>::info(this); }
- };
- // petit test, facile à utiliser finalement ce poolable,
- // mais pas très beau le doublon dans le template
- class Test1: public Poolable<Test1>
- { public: char a[256]; };
- // la même, pas poolable
- class Test2
- { public: char a[256]; };
- // pour comparer les deux tests
- typedef Test2 Test;
- // test...
- int main()
- {
- const int n=maxPoolElements;
- Test* t[n]={0};
- struct timeval tv0; gettimeofday(&tv0,0);
- // 20M de tests
- for(unsigned int i=0;i<20000000/n;i++)
- {
- for(unsigned int j=0;j<n;j++)
- if(t[j])
- { delete t[j]; t[j]=0; }
- else
- t[j]=new Test();
- }
- // temps de test
- struct timeval tv; gettimeofday(&tv,0);
- int usecs=tv.tv_usec-tv0.tv_usec;
- long secs=tv.tv_sec-tv0.tv_sec;
- if(usecs<0) { usecs+=1000000; secs--; }
- printf("%d.%06d\n",secs,usecs);
- // libérer
- for(unsigned int i=0;i<n;i++)
- if(t[i]) delete t[i];
- return 0;
- }
|
Après compilation gcc/linux, j'obtiens les temps suivants:
* pool : 6.397160 sec.
* new/del : 10.888527 sec. |
Mais certains points ne me satifont pas:
- doublon du nom de la classe de base avec la classe poolable
- gestion des erreurs... médiocre
- métadonnées et structures... je ne mesure pas l'impact des compilateurs sur l'alignement/endianness/sizeofs de ma structure d'élément de pile
- gain de temps presque négligeable avec des objets à initialiser
- piles de taille statiques, à rendre plus dynamique(?)
- métadonnées inexploitées...
Si vous avez des remarques ou des suggestions sur mon code, elles sont les bienvenues
Merci par avance ! Message édité par nargy le 31-03-2006 à 00:26:04
|