2009-06-29 4 views
7

J'ai beaucoup utilisé HashSet et Dictionary en C#, et je les ai trouvés très rapidement ...Conteneur C++ rapide comme le C# HashSet <T> et le Dictionnaire <K,V>?

J'ai essayé d'utiliser std :: map et std :: hash_map et je les trouve très lents en comparaison. Est-ce que cela ressemble à un comportement attendu? Y a-t-il quelque chose que je puisse faire de mal dans mon utilisation de std :: hash_map?

Ou, y a-t-il un meilleur conteneur C++ Hash?

Je suis hash int32s, généralement autour de 100 000 d'entre eux.

Mise à jour: J'ai créé un repro en C# et C++. Il court deux essais, ils prennent 19ms et 13ms en C#, et environ 11.000ms en C++. Il doit y avoir quelque chose de vraiment mal avec mon code C++ :)

(Les deux ont été exécutés comme builds de presse, les deux sont des applications de la console)

C# Sortie:

Found 511 values in the intersection, in 19 ms 
Found 508 values in the intersection, in 13 ms 

de sortie de C:

Found 308 values in the intersection, in 11764.7ms 
Found 316 values in the intersection, in 11742.8ms 

Sortie C++ (en utilisant stdext :: hash_map au lieu de std :: map)

Found 300 values in the intersection, in 383.552ms 
Found 306 values in the intersection, in 2277.02ms 

C++ sortie (en utilisant stdext :: hash_map, une version build x64)

Found 292 values in the intersection, in 1037.67ms 
Found 302 values in the intersection, in 3663.71ms 

Notes:

  • Set2 ne reçoit pas rempli tout à fait comme je le voulais en C++, je l'attendais d'avoir une intersection de 50% avec Set1 (comme il le fait en C#), mais je devais multiplier mon nombre aléatoire de 10 pour une raison quelconque, même les amener à se croisent partiellement non

C#:

static void Main(string[] args) 
    { 
     int start = DateTime.Now.Millisecond; 
     int intersectionSize = runIntersectionTest(); 
     int duration = DateTime.Now.Millisecond - start; 

     Console.WriteLine(String.Format("Found {0} values in the intersection, in {1} ms", intersectionSize, duration)); 

     start = DateTime.Now.Millisecond; 
     intersectionSize = runIntersectionTest(); 
     duration = DateTime.Now.Millisecond - start; 

     Console.WriteLine(String.Format("Found {0} values in the intersection, in {1} ms", intersectionSize, duration)); 

     Console.ReadKey(); 
    } 

    static int runIntersectionTest() 
    { 
     Random random = new Random(DateTime.Now.Millisecond); 

     Dictionary<int,int> theMap = new Dictionary<int,int>(); 

     List<int> set1 = new List<int>(); 
     List<int> set2 = new List<int>(); 

     // Create 100,000 values for set1 
     for (int i = 0; i < 100000; i++) 
     { 
      int value = 1000000000 + i; 
      set1.Add(value); 
     } 

     // Create 1,000 values for set2 
     for (int i = 0; i < 1000; i++) 
     { 
      int value = 1000000000 + (random.Next() % 200000 + 1); 
      set2.Add(value); 
     } 

     // Now intersect the two sets by populating the map 
     foreach(int value in set1) 
     { 
      theMap[value] = 1; 
     } 

     int intersectionSize = 0; 

     foreach (int value in set2) 
     { 
      int count; 
      if (theMap.TryGetValue(value, out count)) 
      { 
       intersectionSize++; 
       theMap[value] = 2; 
      } 
     } 

     return intersectionSize; 
    } 

C++:

int runIntersectionTest() 
{ 
    std::map<int,int> theMap; 

    vector<int> set1; 
    vector<int> set2; 

    // Create 100,000 values for set1 
    for (int i = 0; i < 100000; i++) 
    { 
     int value = 1000000000 + i; 
     set1.push_back(value); 
    } 

    // Create 1,000 values for set2 
    for (int i = 0; i < 1000; i++) 
    { 
     int random = rand() % 200000 + 1; 
     random *= 10; 

     int value = 1000000000 + random; 
     set2.push_back(value); 
    } 

    // Now intersect the two sets by populating the map 
    for (vector<int>::iterator iterator = set1.begin(); iterator != set1.end(); iterator++) 
    { 
     int value = *iterator; 

     theMap[value] = 1; 
    } 

    int intersectionSize = 0; 

    for (vector<int>::iterator iterator = set2.begin(); iterator != set2.end(); iterator++) 
    { 
     int value = *iterator; 

     map<int,int>::iterator foundValue = theMap.find(value); 

     if (foundValue != theMap.end()) 
     { 
      theMap[value] = 2; 

      intersectionSize++; 
     } 
    } 

    return intersectionSize; 

} 

int _tmain(int argc, _TCHAR* argv[]) 
{ 
    srand (time(NULL)); 

    Timer timer; 
    int intersectionSize = runIntersectionTest(); 
    timer.Stop(); 

    cout << "Found " << intersectionSize << " values in the intersection, in " << timer.GetMilliseconds() << "ms" << endl; 

    timer.Reset(); 
    intersectionSize = runIntersectionTest(); 
    timer.Stop(); 

    cout << "Found " << intersectionSize << " values in the intersection, in " << timer.GetMilliseconds() << "ms" << endl; 

    getchar(); 

    return 0; 
} 
+1

Pourriez-vous fournir quelques repères? –

+0

Ce qui prend peut-être 10 ms en C# semble prendre 1000ms en C++. Je vais essayer de faire une comparaison plus contrôlée demain, peut-être du code postal pour chaque C# et C++. –

+0

J'ai posté quelques benchmarks. –

Répondre

5

Hash_map et hash_set ne sont pas standard, unordered_map et unordered_set sont les versions les plus susceptibles d'être bientôt standard. Sans avoir de reproducteur, je ne pense pas que cela va aller loin.Sous le capot, ce sont les mêmes structures de données, elles devraient donc avoir des performances similaires.


Je Compilé l'échantillon fourni sous MS Visual Studio 2008 v9.0.30729.1, comme Visual C++ -> Win32 -> Application console (si je roulais ma propre classe Timer parce que je ne savais pas ce que vous étiez en utilisant). Sous debug, j'ai des temps de 1000 ms, mais la compilation sous release était de 50 ms.

#include <vector> 
#include <iostream> 
#include <map> 
#include <stdio.h> 
#include <stdlib.h> 
#include <time.h> 

#include <windows.h> 

typedef struct { 
    LARGE_INTEGER start; 
    LARGE_INTEGER stop; 
} stopWatch; 

class CStopWatch { 

private: 
    stopWatch timer; 
    LARGE_INTEGER frequency; 
    double LIToSecs(LARGE_INTEGER & L); 
public: 
    CStopWatch(); 
    void startTimer(); 
    void stopTimer(); 
    double getElapsedTime(); 
}; 

double CStopWatch::LIToSecs(LARGE_INTEGER & L) { 
    return ((double)L.QuadPart /(double)frequency.QuadPart) ; 
} 

CStopWatch::CStopWatch(){ 
    timer.start.QuadPart=0; 
    timer.stop.QuadPart=0; 
    QueryPerformanceFrequency(&frequency) ; 
} 

void CStopWatch::startTimer() { 
    QueryPerformanceCounter(&timer.start) ; 
} 

void CStopWatch::stopTimer() { 
    QueryPerformanceCounter(&timer.stop) ; 
} 

double CStopWatch::getElapsedTime() { 
    LARGE_INTEGER time; 
    time.QuadPart = timer.stop.QuadPart - timer.start.QuadPart; 
    return LIToSecs(time) ; 
} 

using namespace std; 
int runIntersectionTest() 
{ 
    std::map<int,int> theMap; 

    vector<int> set1; 
    vector<int> set2; 

    // Create 100,000 values for set1 
    for (int i = 0; i < 100000; i++) 
    { 
     int value = 1000000000 + i; 
     set1.push_back(value); 
    } 

    // Create 1,000 values for set2 
    for (int i = 0; i < 1000; i++) 
    { 
     int random = rand() % 200000 + 1; 
     random *= 10; 

     int value = 1000000000 + random; 
     set2.push_back(value); 
    } 

    // Now intersect the two sets by populating the map 
    for (vector<int>::iterator iterator = set1.begin(); iterator != set1.end(); iterator++) 
    { 
     int value = *iterator; 

     theMap[value] = 1; 
    } 

    int intersectionSize = 0; 

    for (vector<int>::iterator iterator = set2.begin(); iterator != set2.end(); iterator++) 
    { 
     int value = *iterator; 

     map<int,int>::iterator foundValue = theMap.find(value); 

     if (foundValue != theMap.end()) 
     { 
       theMap[value] = 2; 

       intersectionSize++; 
     } 
    } 

    return intersectionSize; 

} 

int main(int argc, char* argv[]) 
{ 
    srand (time(NULL)); 
    int tests = 2; 
    while(tests--){ 
     CStopWatch timer; 
     timer.startTimer(); 
     int intersectionSize = runIntersectionTest(); 
     timer.stopTimer(); 

     cout << "Found " << intersectionSize << " values in the intersection, in " << timer.getElapsedTime() << "s\r\n"; 
    } 

    getchar(); 

    return 0; 
} 

(Je voudrais essayer avec unordered_map mais ma version ne l'a pas). Je suppose qu'il y a un problème dans votre configuration pour C++.

+0

Note: Boost offre la mise en œuvre des deux. – GManNickG

+1

J'ai trouvé quelque chose: si j'attache le débogueur à des versions RELEASE ou DEBUG (par exemple, appuyez sur F5 dans l'EDI), alors j'ai des temps horribles. –

0

Il ne semble pas prévu, mais vous aurez besoin de recueillir quelques détails avant de pouvoir vraiment aider. Avec quelle implémentation de hash_map utilisez-vous? Avez-vous pointé un profileur, et si oui, que vous a-t-il dit?

En général, si une implémentation de table de hachage fonctionne mal sans raison apparente, c'est généralement parce que la fonction de hachage que la table utilise se comporte mal pour votre entrée particulière. Cela pourrait être votre problème - le C hash_map arrive à utiliser une fonction de hachage qui mappe vos clés à un petit nombre de compartiments, et le C# HashSet ne le fait pas - ou peut-être quelque chose de complètement différent. Std :: map est généralement implémenté comme un arbre, et aura donc des caractéristiques de performance différentes. Encore une fois, les détails de la mise en œuvre et des données d'entrée sont importants.

+0

Lorsque j'ai utilisé hash_map, je crois que j'utilisais Microsoft ... Je viens de démarrer VS 2008 et j'ai tapé #include . Des conseils sur une bonne fonction de hachage avec hash_map pour les nombres Int32? Je vais faire un peu de googling. –

+0

L'équipe VC++ est assez pointue sur ce genre de chose IME, ce qui me fait penser qu'il est moins probable qu'il s'agisse d'un problème de fonction de hachage.Je regarderai le problème plus en profondeur après que vous ayez posté l'exemple de code demain. –

+0

exemple de code affiché. –

0

Vous utilisez std :: carte dans votre code C++, qui a des temps d'insertion et de consultation de O (log (n)). Essayez de tester avec hash_map pour obtenir une meilleure comparaison.

+0

J'ai changé std :: map pour stdext :: hash_map, et j'ai obtenu de MEILLEURS résultats, mais toujours terribles par rapport à C#. Trouvées 300 valeurs dans l'intersection, en 383.552ms Trouvées 306 valeurs dans l'intersection, en 2277.02ms –

0

Ce que vous comparez vraiment est

C set # de hachage qui est O (1), ce qui signifie presque constante et indépendante de la taille d'entrée,

contre C++ vecteur .... signification (taille de l'entrée) fois constant ...

Ceci est peu pratique.

Vous devriez essayer d'utiliser l'équivalent de HashSet en C++ qui est (après TR1 en 2007 je pense) std :: tr1 :: unordered_set < ...> (et std :: tr1 :: unordered_set < ... >)

wikipedia link on TR1

Notez également que selon this page Visual studio a sa propre implémentation de TR1 stl suboptimale. (sans expérience personnelle, l'ai trouvé here)

Questions connexes