2009-06-29 6 views
1

Ich überschneide eine Reihe von 100.000 Zahlen und eine Menge von 1.000 Zahlen mit set_intersection in STL und seine Einnahme von 21s, wo es 11ms in C# dauert.Warum ist set_intersection in STL so langsam?

C++ Code:

int runIntersectionTestAlgo() 
{ 

    set<int> set1; 
    set<int> set2; 
    set<int> intersection; 


    // Create 100,000 values for set1 
    for (int i = 0; i < 100000; i++) 
    { 
     int value = 1000000000 + i; 
     set1.insert(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.insert(value); 
    } 

    set_intersection(set1.begin(),set1.end(), set2.begin(), set2.end(), inserter(intersection, intersection.end())); 

    return intersection.size(); 
} 

C# Code:

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; 
    } 
} 
+0

Dies ist kein nützlicher Kommentar: Timing Sie das gesamte Programm oder nur den Aufruf von set_intersection()? – Pod

+0

Takten Sie sowohl die Erstellung der Anfangssätze als auch die Schnittmenge? –

+2

Sie erkennen, dass C++ std :: set eine baumbasierte Struktur ist, während das C# -Dictionary eine Array-basierte Hashtabelle ist, und List ist nur ein Array, richtig? Bevor Sie die Zuordnungsprobleme Ihres Codes berücksichtigen, vergleichen Sie Äpfel und Orangen miteinander. –

Antwort

2

Auf diesem alten 3GHz Pentium 4, bekomme ich 2734 Millisekunden für die gesamte runIntersectionTestAlgo Funktion, in einem Debug-Build mit deaktivierten Optimierungen. Ich habe mit VS2008 SP1 kompiliert.

Wenn ich Optimierungen aktivieren, bekomme ich 93 Millisekunden.

Hier ist mein Code:

#include <set> 
#include <algorithm> 

using namespace std; 

int runIntersectionTestAlgo() 
{ 

    set<int> set1; 
    set<int> set2; 
    set<int> intersection; 


    // Create 100,000 values for set1 
    for (int i = 0; i < 100000; i++) 
    { 
     int value = 1000000000 + i; 
     set1.insert(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.insert(value); 
    } 

    set_intersection(set1.begin(),set1.end(), set2.begin(), set2.end(), inserter(intersection, intersection.end())); 

    return intersection.size(); 
} 

#include <windows.h> 
#include <iostream> 

int main(){ 
    DWORD start = GetTickCount(); 

    runIntersectionTestAlgo(); 

    DWORD span = GetTickCount() - start; 

    std::cout << span << " milliseconds\n"; 
} 

Deaktivieren _SECURE_SCL keinen Unterschied für die Release-Build gemacht, die um die 100 ms schwebte noch.

GetTickCount ist natürlich nicht ideal, aber es sollte gut genug sein, um 21 Sekunden von weniger als 100 Millisekunden zu unterscheiden.

Also ich schließe, dass etwas mit Ihren Benchmarks nicht stimmt.

+0

Wow. Ich nahm Ihren Code, lief es und es lief in 31ms. Ich habe es dann mit dem angehängten Debugger laufen lassen (wie ich es für meine Tests gemacht hatte) und es lief in 23353 ms. –

+0

STL verwendet viele Zwischenfunktionen, die inline sein müssen, damit die Leistung akzeptabel ist. Mit GCC können Sie Debuggen und Optimieren gleichzeitig aktivieren, und aktuelle GDB-Versionen können inlined Funktionsaufrufe anzeigen und durchlaufen, so dass Sie diese Dinge mit erträglicher Geschwindigkeit debuggen können;) Sie könnten versuchen, ohne weitere Optimierungen Inlining zu aktivieren und zu sehen, welche Art von der Geschwindigkeit, die du bekommst. –

8

Ein paar Dinge, würde Ihre zwei Beispiele besser vergleichbar machen.

Zuerst ist Ihr Beispiel in STL nicht ganz richtig, für eine Sache sollten beide Sätze in aufsteigender Reihenfolge sortiert werden (in STL spricht eine "strenge schwache Ordnung").

Zweitens, verwenden Sie "Sets", die als Bäume in STL implementiert sind, vs. "Listen", die verknüpfte Listen sind. Zufällige Einfügungen in ein Set sind teurer als das Einfügen am Ende einer Liste.

Versuchen Sie es mit einer Liste von Ints im C++ - Beispiel und sortieren Sie auch die Liste zuerst (ansonsten wird die Inersektion nicht richtig funktionieren) und ich denke, Sie werden bessere Ergebnisse sehen.

5

lief ich Ihre C++ Code auf meinem Linux-Box

$ time ./test 

real 0m0.073s 
user 0m0.060s 
sys  0m0.003s 

21s mir bedeutet, dass Sie ohne Optimierungen zusammengestellt. Wenn Sie MSVC verwenden, stellen Sie sicher, dass Sie _SECURE_SCL=0 (siehe msdn) in den Kompilierdefinitionen aufgelistet haben. Ansonsten sind alle STL-Iterator-Operationen langsam.

+0

+1 für _SCL_SECURE. Ich hatte vorher noch nie von dieser Flagge gehört. Weißt du, ob es für Release-Builds deaktiviert ist? –

+1

Dies war Thema der Diskussion auf der Boost-Mailing-Liste. Sie sagten, _SCL_SECURE ist standardmäßig aktiviert (auf 1 gesetzt), selbst im Release-Modus. –

+0

Es ist standardmäßig in Debug- und Release-Builds aktiviert. Laut einer Präsentation auf der diesjährigen BoostCon wird es in Release-Builds in VS2010 deaktiviert. :) – jalf

1

Ich habe Ihr Beispiel aktualisiert, um einen Timercode zu verwenden, den ich beim Komponententest verwende. Auf meinem Rechner bekomme ich die folgenden Timings (basierend auf O3):

First loop 0.0040654 
Second loop 4.8e-05 
Intersection 0.000349 
Intersection size: 50 

auf dieser Grundlage, wenn ich meine Dezimalstellen richtig gerade lese, ist es 4ms 'nimmt die Elemente in den ersten Satz einzufügen, 50 Mikrosekunden zum Einfügen der Elemente in den zweiten Satz und 1/3 ms zum Ausführen der Schnittmenge.

Ich kann Ihr C# -Beispiel nicht auf meinem Computer ausführen, daher kann ich das Timing nicht vergleichen, aber es sind definitiv keine 21s, wie Sie schreiben.

0

Ihr C# - und C++ - Code funktioniert anders. Der C# -Code verwendet magische Hashing-Tricks für Geschwindigkeit, Ihr C++ - Code verwendet Baumtricks für Geschwindigkeit.Eine Sache, die wahrscheinlich die Dinge beschleunigen wird bis (ohne Berücksichtigung der Tatsache, dass Ihre Tests scheint gebrochen zu werden) wäre Hashing verwendet wird, wie folgt:

  1. erstellen hash_map von einem der beiden Sammlungen.
  2. Iterieren über jedes Element in der 2. Sammlung. Wenn die `hash_map1 dieses Element enthält, fügen Sie es zu Ihrem Ergebnis hinzu.
+0

Ja, sie sind anders. Ich habe vorher den C# -Ansatz in C++ mit std: map, stdext: hash_map und boost :: unordered_set probiert und bekam die gleichen schlechten Ergebnisse. Ich bin mir sicher, wenn ich den C# -Code geändert habe, um HashSet und .IntersectWith zu verwenden, wäre es so schnell (oder vielleicht schneller). –