IdentifiantMot de passe
Loading...
Mot de passe oublié ?Je m'inscris ! (gratuit)
Navigation

Inscrivez-vous gratuitement
pour pouvoir participer, suivre les réponses en temps réel, voter pour les messages, poser vos propres questions et recevoir la newsletter

SL & STL C++ Discussion :

Tableaux associatifs fixes


Sujet :

SL & STL C++

  1. #1
    Invité
    Invité(e)
    Par défaut Tableaux associatifs fixes
    Bonjour,

    L'application sur laquelle je travaille utilise beaucoup de tableaux associatifs. Certains d'entre eux sont très gros (le principal pourrait avoir, en régime de croisière plusieurs centaines de milliers, peut être un million d'entrées), d'autres sont plus petits (quelques milliers d'entrées), mais tous sont très sollicités à l'exécution.

    Ces tableaux associatifs partagent tous une caractéristique commune : on n'y fait ni insertion, ni suppression. Ils sont lus au démarrage de l'appli (ou la première fois qu'on en a besoin), dans des fichiers produits par une autre application (je peux en changer le format si nécessaire). Aujourd'hui ils sont indexés par des string.

    En première approche, j'ai utilisé des map, que je charge à partir de fichiers triés selon la clef. Quelque chose comme

    Et je fais appel à find() pour les recherches. Sur un fichier déjà trié, le chargement se fait en temps linéaire, et les recherches en temps logarithmique.

    Après réflexion, il me semble qu'un
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    vector<pair<string,MonType> >
    trié dans l'ordre des clefs, et une recherche binaire, fonctionnerait mieux. A priori, le chargement est aussi linéaire (mais certainement de coefficient dominant plus faible que pour la map), la recherche aussi logarithmique, et l'empreinte mémoire sans doute plus faible...

    Est ce bien le cas?

    Du coup, je me demande si je peux améliorer cela...

    J'ai réfléchi à deux solutions, par forcément exclusives l'une de l'autre

    1- une réindexation de mes codes, destinée à remplacer les string par des entiers. Les recherches binaires effectuant des comparaisons assez nombreuses (entre 10 et 20 par recherche dans mon cas), utiliser une comparaison d'entier devrait significativement améliorer le temps de recherche, et également réduire l'empreinte mémoire de mon tableau...

    2- l'utilisation d'un tableau haché (hash_map), les codes de hachage étant précalculés dans le fichier initial (pour éviter de les refaire au chargement), mais du coup, je me demande si cela n'est pas strictement équivalent à ma réindexation...

    Qu'en pensez vous? Qu'utiliseriez vous?

    (je précise que je suis sous Borland Builder 6, que ma STL est une STLPort, et que seuls des bouts de boost fonctionnent, en revanche, je n'aurais aucun complexe à me lancer dans un codage d'algorithme spécifique, cette recherche est réellement le coeur de l'application).

    Merci d'avance
    Francois

  2. #2
    Rédacteur/Modérateur
    Avatar de JolyLoic
    Homme Profil pro
    Développeur informatique
    Inscrit en
    Août 2004
    Messages
    5 463
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 50
    Localisation : France, Yvelines (Île de France)

    Informations professionnelles :
    Activité : Développeur informatique
    Secteur : High Tech - Éditeur de logiciels

    Informations forums :
    Inscription : Août 2004
    Messages : 5 463
    Par défaut
    Un vecteur de pair risque de poser problème pour les grands tableaux, car il demande à ce que la mémoire soit continue.

    Je n'ai pas trop compris comment tu comptes remplacer des strings par des entiers ?

    Est-ce que tu vas souvent chercher les mêmes données ? Il y a des structures d'arbres comme une map mais qui vont réordonner des données à chaque interrogation de telle manière que les demandes fréquentes soient plus proches du sommet.

    Une hashmap, pourquoi pas, mais en faisant bien attention d'éviter les doublons.
    Tes strings sont-ils semblables d'une exécution à l'autre ? Tu pourrais dans ce cas faire un test de la fonction de hash voir ce qu'elle vaut ?
    Ma session aux Microsoft TechDays 2013 : Développer en natif avec C++11.
    Celle des Microsoft TechDays 2014 : Bonnes pratiques pour apprivoiser le C++11 avec Visual C++
    Et celle des Microsoft TechDays 2015 : Visual C++ 2015 : voyage à la découverte d'un nouveau monde
    Je donne des formations au C++ en entreprise, n'hésitez pas à me contacter.

  3. #3
    Invité
    Invité(e)
    Par défaut
    Citation Envoyé par JolyLoic Voir le message
    Un vecteur de pair risque de poser problème pour les grands tableaux, car il demande à ce que la mémoire soit continue.
    Effectivement... En fait, je me rend compte qu'il n'est pas nécessaire de stocker ensembles les clefs et les valeurs, mais juste d'associer à chaque clef "quelque chose" qui permettre de retrouver la valeur correspondante...

    On pourrait donc avoir soit un vecteur de pair<clef,adresse> (l'adrresse étant un indice dans le tableau des valeurs), soit même juste un vecteur de clefs, en utilisant la position renvoyée comme l'indice dans un vecteur de valeurs...

    Merci beaucoup pour la suggestion...

    Citation Envoyé par JolyLoic Voir le message
    Je n'ai pas trop compris comment tu comptes remplacer des strings par des entiers ?
    En fait, les string qui me servent de clefs sont assez peu utilisées pour elles mêmes (essentiellement lors de sauvegardes), et généralement utilisées comme index dans mes tables associatives. Dans la mesure où mes tables ne bougent pas d'une éxécution à l'autre, je me disais que je pourrais peut être tout réindexer avant l'exécution, en associant à chaque string un entier unique, et en remplaçant, partout où je peux, le string par l'entier... Ca ressemble à la notion d'index de table dans les Bases de Données, si tu veux.

    De cette façon mes recherches se font sur des entiers, au lieu des strings, mais je conserve dans un coin la correspondance "index clef", pour les quelques cas ou je dois utiliser les clefs...

    Citation Envoyé par JolyLoic Voir le message
    Est-ce que tu vas souvent chercher les mêmes données ? Il y a des structures d'arbres comme une map mais qui vont réordonner des données à chaque interrogation de telle manière que les demandes fréquentes soient plus proches du sommet.
    Tu m'intéresses ! En fait, je pense que je vais avoir à peu près 500 000 entrées dans le tableau principal, mais qu'à l'éxécution, il sera rare qu'on n'ait besoin de plus d'une centaine. En fait, je sais même assez précisément (à l'exécution) quelles entrées vont revenir (certaines opérations utilisent des entrées juste une fois, d'autres indiquent qu'une entrée va souvent servir...)

    En y repensant, je suis même probablement capable de classer a priori mes variables en fonction de leur fréquence d'utilisation... Peut on fabriquer une map qui prenne ces éléments en compte?

    Une petite référence livresque me serait bien utile là?

    Citation Envoyé par JolyLoic Voir le message
    Tes strings sont-ils semblables d'une exécution à l'autre ? Tu pourrais dans ce cas faire un test de la fonction de hash voir ce qu'elle vaut ?
    Oui, c'est en fait un peu ce qui m'a retenu jusqu'ici, le fait de devoir trouver une fonction de hachage valable...

    Francois

  4. #4
    Rédacteur/Modérateur
    Avatar de JolyLoic
    Homme Profil pro
    Développeur informatique
    Inscrit en
    Août 2004
    Messages
    5 463
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 50
    Localisation : France, Yvelines (Île de France)

    Informations professionnelles :
    Activité : Développeur informatique
    Secteur : High Tech - Éditeur de logiciels

    Informations forums :
    Inscription : Août 2004
    Messages : 5 463
    Par défaut
    Citation Envoyé par fcharton Voir le message
    Une petite référence livresque me serait bien utile là?
    http://en.wikipedia.org/wiki/Splay_tree
    Ma session aux Microsoft TechDays 2013 : Développer en natif avec C++11.
    Celle des Microsoft TechDays 2014 : Bonnes pratiques pour apprivoiser le C++11 avec Visual C++
    Et celle des Microsoft TechDays 2015 : Visual C++ 2015 : voyage à la découverte d'un nouveau monde
    Je donne des formations au C++ en entreprise, n'hésitez pas à me contacter.

  5. #5
    Invité
    Invité(e)
    Par défaut
    Merci beaucoup!

    Francois

  6. #6
    Invité
    Invité(e)
    Par défaut
    J'ai fait des essais toute cette semaine avec des maps de quelques dizaines de milliers d'éléments (parfois plus), indexées soit par des int soit par des string...

    En gros, je les charge à partir de fichiers déjà triés (le manuel me dit que cela garantit de meilleurs temps de chargement, enfin je crois), et je les utilise pour des recherches.

    Je les ai comparées à une implémentation utilisant deux vecteurs, un trié pour les clefs, l'autre pour les valeurs, et un opérateur[] surchargé utilisant la fonction lower_bound().

    Ma conclusion du moment est : pas de maps. En gros, le chargement des maps est environ 8 fois plus lent que celui des vecteurs, et la recherche même pas plus rapide. D'autres ont ils essayé et rencontré se problème, ou y aurait il un gros piège dans lequel je suis tombé (ca me ressemblerait...)?

    Du coup, j'ai une question annexe : la structure que j'utilise a grosso modo cette forme

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
     
    template<typename S,typename T> class MaMappe {
      vector<S> Idx;
      vector<T> Data;
    }
    Idx contient les clefs, Data les données. De temps en temps, il peut m'arriver d'avoir à réordonner une telle structure. Je sais trier mes clefs avec sort, bien sur, mais comment faire pour réordonner automatiquement Data (sans avoir à recopier les données...)?

    Dans le même ordre d'idées, y a-t-il un moyen simple (dans la STL) pour, au lieu de trier explicitement les données d'un vecteur, construire un indice permettant de parcourir ce vecteur dans l'ordre de tri? Un paramètre à passer à sort(), une astuce comme cela?

    Merci d'avance
    Francois

  7. #7
    Membre chevronné
    Inscrit en
    Août 2004
    Messages
    556
    Détails du profil
    Informations forums :
    Inscription : Août 2004
    Messages : 556
    Par défaut
    Citation Envoyé par fcharton Voir le message
    Je sais trier mes clefs avec sort, bien sur, mais comment faire pour réordonner automatiquement Data (sans avoir à recopier les données...)?
    Tu n'as pas besoin de copier les données, uniquement les clefs (et ceci en attendant la move semantics. Une fois que l'ordre des clefs avant le tri, il te suffit d'effectuer des swap successifs sur tes données. Mais, ceci sera en temps linéaire. Utiliser une map permet d'éviter cela (j'y arriverai)

    Ca m'étonne fortement que map soit plus lent que vector. Comment insères-tu tes éléments ? Un par un ? Si oui, voilà ton erreur naïve.

    10,000 éléments à insérer un par un, bien sûr que ça sera plus lent qu'un vector ou une list. C'est pas fait pour. Dans ce genre de conteneur, tu n'insères généralement pas un à un (sauf si tu n'as pas le choix, ou que tu n 'as qu'un élément à insérer), mais tu insères des "ranges".

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    template < class Key, class Data, class Compare, class Alloc > 
    class map
    {
       template < class InputIterator >
       void insert(InputIterator, InputIterator)
    };
    à la limite, tu peux utiliser un vecteur comme transition si la manière dont tu récupère les éléments est purement itératif, ensuite tu insères ton vector< pair > dant ta map.

    Bien sûr le chargement sera sensiblement plus long que de garder ton vector, mais les problèmes que tu te poses ne se poseront plus ensuite. La recherche quant à elle ne peut être plus longue qu'un vector. Rappelle: vector::find() = O(N) là où map::find() = O( log(N) )

    De plus, une map est toujours triée. Le problème ne se pose donc pas pour le tri.

  8. #8
    Invité
    Invité(e)
    Par défaut
    Citation Envoyé par JulienDuSud Voir le message
    10,000 éléments à insérer un par un, bien sûr que ça sera plus lent qu'un vector ou une list. C'est pas fait pour. Dans ce genre de conteneur, tu n'insères généralement pas un à un (sauf si tu n'as pas le choix, ou que tu n 'as qu'un élément à insérer), mais tu insères des "ranges".
    En es tu bien sur? Voici, sur la distribution de stl que j'utilise (stlport), le code de l'insertion d'un range dans un conteneur associatif à clef unique

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
      void insert_unique(const_iterator __first, const_iterator __last) {
        for ( ; __first != __last; ++__first)
          insert_unique(*__first);
      }
    L'insertion par range se contente d'appeler, autant de fois qu'il y a d'éléments, l'insertion unitaire. Cela permet peut être de petites optimisations pour le compilateur, mais je doute que cela fasse une grande différence. Je crois que cela tient au fait que la map est représentée par un arbre, qui ne sait pas faire d'insertions multiples.

    En fait, mon manuel (stlport toujours) dit ceci :

    Insert with hint is logarithmic in general, but it is amortized constant time if t is inserted immediately before p.

    Insert range is in general O(N * log(N)), where N is the size of the range. However, it is linear in N if the range is already sorted by value_comp().
    Ce que je comprends, c'est que la vitesse d'insertion d'un range est algorithmiquement le produit du nombre d'élements (N) par la complexité de chaque insertion (log N dans le cas général). Le seul moyen d'aller vite est donc d'insérer un tableau trié, on est alors linéaire (comme pour un vecteur).

    Mais ca restera toujours plus lent qu'un vecteur, car la structure sous jacente de la map est bien plus complexe que celle du vecteur...

    Citation Envoyé par JulienDuSud Voir le message
    La recherche quant à elle ne peut être plus longue qu'un vector. Rappelle: vector::find() = O(N) là où map::find() = O( log(N) )
    Oui, dans la mesure ou ton vector est dans un ordre quelconque. Les miens sont triés (la donnée de départ l'est...), donc je n'utilise pas find mais lower_bound, qui lui cherche en temps logarithmique. En terme de complexité algorithmique, c'est exactement la même chose que pour la map... Mais là encore je soupconne que la structure beaucoup plus simple du vecteur fait gagner du temps (la recherche dans une map doit impliquer des indirections, je pense, faudrait voir...).

    En fait, je pense qu'on pourrait même avoir des cas où le vecteur est nettement plus efficace (par exemple avec des clefs uniformément distribuées, on pourrait rechercher par interpolation, et là on est, algorithmiquement O(log log n) ).

    Plus j'y réfléchis, plus je me dis qu'un vecteur trié sera toujours plus performant qu'une map, en termes de mémoire, et en termes de vitesse. Tant que les données ne changent pas, bien sur.


    Citation Envoyé par JulienDuSud Voir le message
    Tu n'as pas besoin de copier les données, uniquement les clefs (et ceci en attendant la move semantics. Une fois que l'ordre des clefs avant le tri, il te suffit d'effectuer des swap successifs sur tes données. Mais, ceci sera en temps linéaire. Utiliser une map permet d'éviter cela (j'y arriverai)
    Je crois que je vois ce que tu veux dire. Au lieu d'avoir un vecteur de clefs, on pourrait avoir un vecteur de paires clefs-positions, le second membre servant d'indice de tri. Quelque chose comme

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
     
    vector<pair<key_type,int> > Keys;
    vector<value_type> Values;
     
    avec
     
    Keys[i].second=i;
    On peut alors trier Keys sur Keys[i].first, et Keys[i].second sera alors l'indice correspondant dans Value (que l'on n'a plus besoin de trier).

    Mais je ne crois pas que je comprends ton affaire de swap... Si je connais les indices d'arrivée de mes éléments de Value, comment utiliser swap pour réordonner? Il va me falloir une table intermédiaire, non?

    Francois

  9. #9
    Invité
    Invité(e)
    Par défaut chargements de set et de vector
    Comme le sujet me travaille, ce soir, j'ai fait quelques tests, avec un profileur.

    Le principe du programme est le suivant.

    1- je charge 1 000 000 de doubles dans un tableau dynamique, soit déjà trié, soit aléatoire
    2- je charge ces éléments dans des set et des vector, selon les deux méthodes "un par un" ou "par range" (si le tableau est trié, je donne un indice pour le set)
    3- dans le cas d'un tableau aléatoire, je trie mes vecteurs
    4- je jette tout cela dans un profileur...

    En gros, ca donne le programme suivant :

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    vector<double> v1,v2;
    set<double> m1,m2;
     
    int loadvect(double *dtab,int n,bool tosort)
    {
    for(int i=0;i<n;i++) v1.push_back(dtab[i]);
    if(tosort) sort(v1.begin(),v1.end());
    }
     
    int loadvectrange(double *dtab,int n,bool tosort)
    {
    v2.insert(0,dtab,dtab+n);
    if(tosort) sort(v2.begin(),v2.end());
    }
     
    int loadset(double *dtab,int n,bool sorted)
    {
    if(sorted) for(int i=0;i<n;i++) m1.insert(m1.end(),dtab[i]);
    else  for(int i=0;i<n;i++) m1.insert(dtab[i]);
    }
     
    int loadsetrange(double *dtab,int n)
    {
    m2.insert(dtab,dtab+n);
    }
     
    int main(int argc, char* argv[])
    {
            double *dtab=new double[1000000];
            iota(dtab,dtab+1000000,1.0);
            loadvect(dtab,1000000,false);
            loadvectrange(dtab,1000000,false);
            loadset(dtab,1000000,true);
            loadsetrange(dtab,1000000);
     
            srand(281);
            for(int i=0;i<1000000;i++) dtab[i]=rand();
            loadvect(dtab,1000000,true);
            loadvectrange(dtab,1000000,true);
            loadset(dtab,1000000,false);
            loadsetrange(dtab,1000000);
            return 0;
    }
    Voici mes timings (Borland C++, STL Port, compilation optimisée, AQTime 3):

    Données triées :
    vector : 0.021
    vector range : 0.008
    set : 0.272 (avec hint tenant compte du tri)
    set range : 0.658

    Données non triées (vecteurs chargés puis triés)
    vector : 0.300
    vector range : 0.291
    set : 0.428
    set range : 0.414

    Sur mon système, les vecteurs sont donc toujours plus rapides. D'un ordre de grandeur dans le cas de données triées, de 30% dans le cas de données aléatoires.

    L'utilisation d'une insertion par range est très utile pour les vecteurs (cas trié). Pour des données non triées, il y a gain de temps, mais faible (de l'ordre de 3%)

    En revanche, pour des sets triés, l'absence d'un "hint" dans la commande insert se révèle fatale à l'insertion par range... Il faut donc s'en méfier.

    Ma conclusion temporaire est donc : les set et les map, c'est (peut être) bien si le tableau varie pendant l'exécution, mais pour un tableau fixe, ou qui change très rarement, rien ne vaut un vecteur trié.

    Je suis bien sur preneur de tout autre timing, ou tout commentaire, suggestion d'amélioration, ou révélation d'une erreur grossière.

    Francois

  10. #10
    Membre Expert

    Inscrit en
    Mai 2008
    Messages
    1 014
    Détails du profil
    Informations forums :
    Inscription : Mai 2008
    Messages : 1 014
    Par défaut
    Citation Envoyé par fcharton Voir le message
    Je suis bien sur preneur de tout autre timing, ou tout commentaire, suggestion d'amélioration, ou révélation d'une erreur grossière.
    Ok, quelques remarques en vrac :

    1) La différence de timing entre l'insertion par push_back et l'insertion par range dans le vecteur :
    C'ets normal, car la version par range préalloue à la bonne taille avant de copier (donc pas de réallocation intempestives si la capacité est atteinte en cours de copie). On peut obtenir le même résultat avec des push_back en utilisant vector::reserve avant de copier.

    En règle générale il est toujours préférable d'utiliser les algorithmes "à range" en utilisant la STL. Par exemple, au lieu de faire :
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    std::vector<int> v2;
    v2.resize(v1.size());
    for(int i = 0; i < v1.size() ; i++) v2[i] = v1[i];
    faire:
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    std::vector<int> v2;
    v2.assign(v1.begin(), v1.end());
     
    // ou encore mieux
    std::vector v2(v1.begin(), v1.end()
    Car le resize appelle explicitement le constructeur par défaut du type présent dans le vector lorsqu'il redimensionne le vecteur, donc il y a une passe en plus.
    De même, on voit souvent ce genre de code :
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    std::copy(v2.begin(), v2.end(), std::back_inserter(v1));
    qui insère les éléments un par un, alors que la version avec range est pourtant plus claire (IHMO), plus courte et plus efficace :
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    v1.insert(v1.end(), v2.begin(), v2.end());

    2)
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    for(int i=0;i<1000000;i++) dtab[i]=rand();
     
    // Real men do
    generate(dtab, dtab + 1000000, rand);
    Just kiddin'

    3)
    Ma conclusion temporaire est donc : les set et les map, c'est (peut être) bien si le tableau varie pendant l'exécution, mais pour un tableau fixe, ou qui change très rarement, rien ne vaut un vecteur trié.
    Je ne comprends pas très bien le but du bench en fait. Pourquoi le temps de chargement du vecteur/map est si important ? C'est surtout l'opération de lookup qu'il faudrait bencher dans l'histoire, non ?

    Un dernier truc. J'avais fait il y a quelques temps des benchs similaires (set/vector). Il y a effectivement un joli piège. Il ne faut surtout pas tester qu'avec des int ou des doubles mais aussi avec des structures plus larges, car la tendance peut s'inverser et le set repasser devant dans certains cas.

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
    87
    88
    89
    90
    91
    92
    93
    94
    95
    96
    97
    98
    99
    100
    101
    102
    103
    104
    105
    106
    107
    108
    109
    110
    111
    112
    113
    114
    115
    116
    117
    118
    119
    120
    121
    122
    123
    124
    125
    126
    127
    128
    129
    130
    131
    132
    133
    134
    135
    136
    137
    138
    139
    140
    141
    142
    143
    144
    145
    146
    147
    148
    149
    150
    151
    152
    153
    154
    155
    156
    157
    158
    159
    160
    161
    162
    163
    164
    165
    166
    167
    168
    169
    170
    171
    172
    173
    174
    175
    176
     
    #ifdef _WIN32
    #include <windows.h>
    struct Chrono
    {
    	LARGE_INTEGER liStart;
    	LARGE_INTEGER liStop;
    	void Start()
    	{
    		QueryPerformanceCounter(&liStart);
    	}
    	double Stop()
    	{
    		QueryPerformanceCounter(&liStop);
    		LONGLONG llTimeDiff = liStop.QuadPart - liStart.QuadPart;
    		// To get duration in milliseconds
    		LARGE_INTEGER Frequency;
    		QueryPerformanceFrequency(&Frequency);
    		return llTimeDiff * 1000.0 / (double) Frequency.QuadPart;
    	}
    };
    #else
    #include <time>
    struct Chrono
    {
    	clock_t stopTime;
    	clock_t startTime;
    	void Start()
    	{
    		startTime = clock();
    	}
    	double Stop()
    	{
    		stopTime = clock();
    		return (stopTime - startTime) / (CLOCKS_PER_SEC / (double) 1000.0); 		
    	}
    };
    #endif
     
    #include <vector>
    #include <algorithm>
    #include <set>
    #include <iostream>
     
    using namespace std;
     
     
    template <typename T>
    double loadvect(T* dtab,int n,bool tosort)
    {
    	vector<T> v1;
    	Chrono c;
    	c.Start();
    	for(int i=0;i<n;i++) v1.push_back(dtab[i]);
    	if(tosort) sort(v1.begin(),v1.end());
    	return c.Stop();
    }
     
    template <typename T>
    double loadvectrange(T* dtab,int n,bool tosort)
    {
    	Chrono c;
    	c.Start();
    	vector<T> v2(dtab, dtab+n);
    	if(tosort) sort(v2.begin(),v2.end());
    	return c.Stop();
    }
     
    template <typename T>
    double loadset(T* dtab,int n, bool tosort)
    {
    	set<T> m1;
    	Chrono c;
    	c.Start();
    	if(tosort) for(int i=0;i<n;i++) m1.insert(dtab[i]); 
    	else for(int i=0;i<n;i++) m1.insert(m1.end(),dtab[i]);
    	return c.Stop();
    }
     
    template <typename T>
    double loadsetrange(T* dtab,int n)
    {
    	Chrono c;
    	c.Start();
    	std::set<T> m2(dtab,dtab+n);
    	return c.Stop();
    }
     
    template <int size>
    struct S
    {
    	S(){}
    	S(char d){d_[0] = d;}
    	char d_[size];
    	bool operator<(const S& s) const {return d_[0] < s.d_[0];} 
    };
     
    #include <fstream>
    #include <iostream>
    #include <vector>
    #include <iterator>
     
    int wmain()
    {
    	bool tosort = true;
            const int size = 1000000;
     
    	double *dtab=new double[size];
    	S<3>* s3tab=new S<3>[size];
    	S<10>* s10tab = new S<10>[size];
    	S<20>* s20tab = new S<20>[size];
    	S<100>* s100tab = new S<100>[size];
     
    	srand(281);
    	generate(dtab, dtab + size, rand); 
    	generate(s3tab, s3tab + size, rand); 
    	generate(s10tab, s10tab + size, rand); 
    	generate(s20tab, s20tab + size, rand); 
    	generate(s100tab, s100tab + size, rand); 
     
     
     
    	std::cout << "\ndouble, sort\n";
    	double time5 = loadvect(dtab,size,tosort);
    	std::cout << time5 <<std::endl;
    	double time6 = loadvectrange(dtab,size,tosort);
    	std::cout << time6 <<std::endl;
    	double time7 = loadset(dtab,size,tosort);
    	std::cout << time7 <<std::endl;
    	double time8 = loadsetrange(dtab,size);
    	std::cout << time8 <<std::endl;
     
     
    	std::cout << "\ns3, sort\n";
    	double time9 = loadvect(s3tab,size,tosort);
    	std::cout << time9 <<std::endl;
    	double time10 = loadvectrange(s3tab,size,tosort);
    	std::cout << time10 <<std::endl;
    	double time11 = loadset(s3tab,size,tosort);
    	std::cout << time11 <<std::endl;
    	double time12 = loadsetrange(s3tab,size);
    	std::cout << time12 <<std::endl;
     
    	std::cout << "\ns10, sort\n";
    	double time13 = loadvect(s10tab,size,tosort);
    	std::cout << time13 <<std::endl;
    	double time14 = loadvectrange(s10tab,size,tosort);
    	std::cout << time14 <<std::endl;
    	double time15 = loadset(s10tab,size,tosort);
    	std::cout << time15 <<std::endl;
    	double time16 = loadsetrange(s10tab,size);
    	std::cout << time16 <<std::endl;
     
    	std::cout << "\ns20, sort\n";
    	double time17 = loadvect(s20tab,size,tosort);
    	std::cout << time17 <<std::endl;
    	double time18 = loadvectrange(s20tab,size,tosort);
    	std::cout << time18 <<std::endl;
    	double time19 = loadset(s20tab,size,tosort);
    	std::cout << time19 <<std::endl;
    	double time20 = loadsetrange(s20tab,size);
    	std::cout << time20 <<std::endl;
     
    	std::cout << "\ns100, sort\n";
    	double time21 = loadvect(s100tab,size,tosort);
    	std::cout << time17 <<std::endl;
    	double time22 = loadvectrange(s100tab,size,tosort);
    	std::cout << time18 <<std::endl;
    	double time23 = loadset(s100tab,size,tosort);
    	std::cout << time19 <<std::endl;
    	double time24 = loadsetrange(s100tab,size);
    	std::cout << time20 <<std::endl;
     
    	delete dtab;
    	return 0;
    }

  11. #11
    Invité
    Invité(e)
    Par défaut
    Merci beaucoup des remarques, Arzar. Mes commentaires sur les tiens...



    Citation Envoyé par Arzar Voir le message
    En règle générale il est toujours préférable d'utiliser les algorithmes "à range" en utilisant la STL.
    Oui, c'est pour cela que j'ai été surpris par la situation dans le cas des set (et donc des maps), ou apparemment le range n'apporte rien (voire dégrade la performance, car il empêche de fournir un indice).

    Citation Envoyé par Arzar Voir le message
    // Real men do
    generate(dtab, dtab + 1000000, rand);[/code]
    Même pas sur, on doit pouvoir trouver, dans boost ou ailleurs, un truc encore plus moderne...

    Real men don't debug their own code...

    Citation Envoyé par Arzar Voir le message
    Je ne comprends pas très bien le but du bench en fait. Pourquoi le temps de chargement du vecteur/map est si important ? C'est surtout l'opération de lookup qu'il faudrait bencher dans l'histoire, non ?
    Oui et non. Dans mon cas, les lookup on les fait un par un, et pas souvent, en plus ils sont logarithmiques. Le chargement on le fait d'un seul coup au début, et il est linéaire. Sur une grosse table, le temps de chargement peut être prohibitif (je déteste les applis qui mettent 30 secondes à démarrer).

    C'est aussi intéressant dans le cas d'un programme qui exploiterait de nombreuses tables, dans lesquelles il fait peu de recherches. Alors, le chargement des données peut facilement dominer le temps de recherche.

    Citation Envoyé par Arzar Voir le message
    Un dernier truc. J'avais fait il y a quelques temps des benchs similaires (set/vector). Il y a effectivement un joli piège. Il ne faut surtout pas tester qu'avec des int ou des doubles mais aussi avec des structures plus larges, car la tendance peut s'inverser et le set repasser devant dans certains cas.
    Je vais regarder cela. A priori, je pense que mon appli n'utilisera pas d'autre index que des int et des string, mais je vais aussi essayer avec des structures plus grosses. Ce que je soupconne c'est que cela ne posera pas de problème tant qu'on évite les recopies du vector. C'est le cas pour un algo de tri qui utilise des swap(), ou mieux, une table lue triée.

    Mais je vais regarder!

    Merci encore!
    Francois

  12. #12
    Membre Expert

    Inscrit en
    Mai 2008
    Messages
    1 014
    Détails du profil
    Informations forums :
    Inscription : Mai 2008
    Messages : 1 014
    Par défaut
    Même pas sur, on doit pouvoir trouver, dans boost ou ailleurs, un truc encore plus moderne...
    Naturellement.
    Les übermen utilisent Boost.Range
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    boost::generate(vec, rand); 
    // equivalent à generate(vec.begin(), vec.end(), rand);
    Oui, c'est pour cela que j'ai été surpris par la situation dans le cas des set (et donc des maps), ou apparemment le range n'apporte rien (voire dégrade la performance, car il empêche de fournir un indice).
    Oui, c'est un cas particulier ici. Avec l'ensemble de départ déjà trié, c'est vrai qu'on peut insérer directement au bon endroit dans le set, donc c'est forcement plus rapide.

    Je vais regarder cela. A priori, je pense que mon appli n'utilisera pas d'autre index que des int et des string
    Ok, donc std::vector semblent être en effet la meilleure solution.


    Qu'est-ce que tu compte faire finalement pour le système de clé et de lookup ? Quelque chose comme ça ?
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
     
    bool operator<(const Index& i1, const Index& i2) const
    { return i1.key < i2.key} 
     
    struct Index
    {
       Index():key(0),object(0){}
       Index(int key, Object* object):key(key),object(object){}
       int key;
       Object* object;
    }
     
    std::vector<Objet> vec; // remplit au démarrage, puis jamais modifié
    std::vector<Index> index; // remplit au démarrage, puis sort()
    La structure Index est une POD, très légère, on peut la copier dans tous les sens pour les sort(). L'opérateur< permet les lower_bound et sera à coup sur inliné, ça a l'air plutôt bien.

  13. #13
    Rédacteur/Modérateur
    Avatar de JolyLoic
    Homme Profil pro
    Développeur informatique
    Inscrit en
    Août 2004
    Messages
    5 463
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 50
    Localisation : France, Yvelines (Île de France)

    Informations professionnelles :
    Activité : Développeur informatique
    Secteur : High Tech - Éditeur de logiciels

    Informations forums :
    Inscription : Août 2004
    Messages : 5 463
    Par défaut
    Citation Envoyé par Arzar Voir le message
    Oui, c'est un cas particulier ici. Avec l'ensemble de départ déjà trié, c'est vrai qu'on peut insérer directement au bon endroit dans le set, donc c'est forcement plus rapide.
    Sauf que l'insertion par range est sensée utiliser cet indice elle aussi :
    Citation Envoyé par Le standard, complexité de l'insertion par range
    Nlog(size()+N) (N
    is the distance from i
    to j) in general;
    linear if [i, j) is
    sorted according to
    value_comp()
    Ma session aux Microsoft TechDays 2013 : Développer en natif avec C++11.
    Celle des Microsoft TechDays 2014 : Bonnes pratiques pour apprivoiser le C++11 avec Visual C++
    Et celle des Microsoft TechDays 2015 : Visual C++ 2015 : voyage à la découverte d'un nouveau monde
    Je donne des formations au C++ en entreprise, n'hésitez pas à me contacter.

  14. #14
    Invité
    Invité(e)
    Par défaut
    Citation Envoyé par Arzar Voir le message
    Naturellement.
    Les übermen utilisent Boost.Range
    Hehe, je sens que je vais rester vieux et ringard, moi... Sérieux, j'ai adoré le C parce qu'il y avait peu de mots à retenir. Je me suis forcé avec le C++, mais je sens que tous ces nouveaux machins ne sont pas compatibles avec mon Alzheimer naissant... Trop de nouveaux mots à retenir, pour éviter d'écrire quelques vieux mots qu'on connait...


    Citation Envoyé par Arzar Voir le message
    Oui, c'est un cas particulier ici. Avec l'ensemble de départ déjà trié, c'est vrai qu'on peut insérer directement au bon endroit dans le set, donc c'est forcement plus rapide.
    Oui, mais comme disait JolyLoic, il se passe quand même un truc étrange. En fait, si je comprends le manuel, l'insert individuel et l'insert par range ne changeront pas grand chose (parce que la structure sous jacente est un arbre, donc de toutes façons, il faut se cogner les réordonnancements élément par élément), mais la complexité d'insert doit être plus faible pour des données triées (range ou pas).

    Or, ce n'est pas le cas chez moi : il semble que si l'on charge par range (ou élément par élément sans hint) un tableau trié dans un set ou une map, on ne va pas si vite. Cela ne veut pas dire que ce n'est pas O(n), ca peut parfaitement l'être avec un gros coefficient dominant, mais bon, c'est un peu étrange quand même.

    Ce n'est pas très grave, remarque: je sais quoi faire pour améliorer la chose, de toutes façons je ne vais pas utiliser des set ou des map, et dans les cas où ils servent (insertion et suppressions aléatoires) les données ne sont pas triées...


    Citation Envoyé par Arzar Voir le message
    Qu'est-ce que tu compte faire finalement pour le système de clé et de lookup ?
    En fait j'ai séparé données et index dans deux vecteurs distincts. L'idée étant à la fois d'utiliser moins de mémoire contigue dans le cas d'un gros tableau (je ne suis pas sur que cela serve...), de simplifier les algos, et de pouvoir, le cas échéant, faire évoluer la structure vers quelque chose qui aurait plusieurs index, façon base de données...

    A ce stade, j'ai construit une classe templates, avec deux vector, un pour les clefs, un pour les données. J'ai surchargé l'opérateur [] (ah oui, petit détail pioché chaipasou, j'ai une entrée "non trouvé" à la fin de mon vecteur objet, pour éviter à [] de lancer des excceptions ou de faire des cochonneries quand on lui passe un mauvais indice).

    En gros, mon interface ressemble à cela (les save et les read ont une interface particulière, c'est ma serialisation maison, et mon alzheimer naissant qui veulent ca):

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    template<typename S,typename T> class FixedMap
    {
    public:
    vector<S> Idx;
    vector<T> Data;
    T &operator[](const S &s) {
            pair<vector<S>::iterator,vector<S>::iterator> > p=lower_bound(Idx.begin(),Idx.end(),s);
            if(p->first==p->second) return Data.back();
            return Data[p-Idx.begin()];
    }
    int size(void) const {return Idx.size();}
    bool IsIn(const S &s) {return (operator[](s)==Data.back());}
    bool operator==(const FixedMap<S,T> &rhc) {
            int sz=Idx.size();
            int sz2=rhc.Idx.size();
            if(sz!=sz2) return false;
            for(int i=0;i<sz;i++) {
                    if(Idx[i]!=rhc.Idx[i] || Data[i]!=rhc.Data[i]) return false;
            }
            return true;
    }
    int sort(void);
    bool merge(const FixedMap<S,T> &extmap);
    void save(ofstream &fdat,ofstream &flib);
    void read(ifstream &fdat,unsigned char **ptr);
    };
    Francois

Discussions similaires

  1. Définition "inline" de tableaux associatifs.
    Par Blustuff dans le forum Général JavaScript
    Réponses: 4
    Dernier message: 29/03/2010, 13h49
  2. Tableaux associatifs et requêtes
    Par LFC dans le forum SGBD
    Réponses: 5
    Dernier message: 28/06/2006, 11h11
  3. Réponses: 9
    Dernier message: 13/06/2006, 21h52
  4. [8i] tableaux associatifs de VARCHAR2
    Par Magnus dans le forum Oracle
    Réponses: 2
    Dernier message: 26/01/2006, 16h41
  5. [Collections]Tableaux associatifs
    Par sheura dans le forum Collection et Stream
    Réponses: 7
    Dernier message: 18/12/2005, 14h10

Partager

Partager
  • Envoyer la discussion sur Viadeo
  • Envoyer la discussion sur Twitter
  • Envoyer la discussion sur Google
  • Envoyer la discussion sur Facebook
  • Envoyer la discussion sur Digg
  • Envoyer la discussion sur Delicious
  • Envoyer la discussion sur MySpace
  • Envoyer la discussion sur Yahoo