Intersezione veloce di insiemi:C++ contro C#
-
21-08-2019 - |
Domanda
Sul mio computer (Quad core, 8 GB di RAM), con Vista x64 Business, con Visual Studio 2008 SP1, sto cercando di intersecare due serie di numeri molto rapidamente.
Ho implementato due approcci in C++ e uno in C#.L'approccio C# è finora più veloce, mi piacerebbe migliorare l'approccio C++ in modo che sia più veloce di C#, cosa che mi aspetto che C++ possa fare.
Ecco l'output in C#:(Build di rilascio)
Found the intersection 1000 times, in 4741.407 ms
Ecco l'output C++ iniziale, per due approcci diversi (build Release x64):
Found the intersection (using unordered_map) 1000 times, in 21580.7ms
Found the intersection (using set_intersection) 1000 times, in 22366.6ms
Ecco l'ultimo output C++, per tre approcci (build Release x64):
Ultimo punto di riferimento:
Found the intersection of 504 values (using unordered_map) 1000 times, in 28827.6ms
Found the intersection of 495 values (using set_intersection) 1000 times, in 9817.69ms
Found the intersection of 504 values (using unordered_set) 1000 times, in 24769.1ms
Pertanto, l'approccio set_intersection è ora circa 2 volte più lento del C#, ma 2 volte più veloce degli approcci iniziali del C++.
Ultimo codice C++:
Code:
// MapPerformance.cpp : Defines the entry point for the console application.
//
#include "stdafx.h"
#include <hash_map>
#include <vector>
#include <iostream>
#include <time.h>
#include <algorithm>
#include <set>
#include <unordered_set>
#include <boost\unordered\unordered_map.hpp>
#include "timer.h"
using namespace std;
using namespace stdext;
using namespace boost;
using namespace tr1;
int runIntersectionTest2(const vector<int>& set1, const vector<int>& set2)
{
// hash_map<int,int> theMap;
// map<int,int> theMap;
unordered_set<int> theSet;
theSet.insert( set1.begin(), set1.end() );
int intersectionSize = 0;
vector<int>::const_iterator set2_end = set2.end();
for ( vector<int>::const_iterator iterator = set2.begin(); iterator != set2_end; ++iterator )
{
if ( theSet.find(*iterator) != theSet.end() )
{
intersectionSize++;
}
}
return intersectionSize;
}
int runIntersectionTest(const vector<int>& set1, const vector<int>& set2)
{
// hash_map<int,int> theMap;
// map<int,int> theMap;
unordered_map<int,int> theMap;
vector<int>::const_iterator set1_end = set1.end();
// Now intersect the two sets by populating the map
for ( vector<int>::const_iterator iterator = set1.begin(); iterator != set1_end; ++iterator )
{
int value = *iterator;
theMap[value] = 1;
}
int intersectionSize = 0;
vector<int>::const_iterator set2_end = set2.end();
for ( vector<int>::const_iterator iterator = set2.begin(); iterator != set2_end; ++iterator )
{
int value = *iterator;
unordered_map<int,int>::iterator foundValue = theMap.find(value);
if ( foundValue != theMap.end() )
{
theMap[value] = 2;
intersectionSize++;
}
}
return intersectionSize;
}
int runSetIntersection(const vector<int>& set1_unsorted, const vector<int>& set2_unsorted)
{
// Create two vectors
std::vector<int> set1(set1_unsorted.size());
std::vector<int> set2(set2_unsorted.size());
// Copy the unsorted data into them
std::copy(set1_unsorted.begin(), set1_unsorted.end(), set1.begin());
std::copy(set2_unsorted.begin(), set2_unsorted.end(), set2.begin());
// Sort the data
sort(set1.begin(),set1.end());
sort(set2.begin(),set2.end());
vector<int> intersection;
intersection.reserve(1000);
set_intersection(set1.begin(),set1.end(), set2.begin(), set2.end(), back_inserter(intersection));
return intersection.size();
}
void createSets( vector<int>& set1, vector<int>& set2 )
{
srand ( time(NULL) );
set1.reserve(100000);
set2.reserve(1000);
// Create 100,000 values for set1
for ( int i = 0; i < 100000; i++ )
{
int value = 1000000000 + i;
set1.push_back(value);
}
// Try to get half of our values intersecting
float ratio = 200000.0f / RAND_MAX;
// Create 1,000 values for set2
for ( int i = 0; i < 1000; i++ )
{
int random = rand() * ratio + 1;
int value = 1000000000 + random;
set2.push_back(value);
}
// Make sure set1 is in random order (not sorted)
random_shuffle(set1.begin(),set1.end());
}
int _tmain(int argc, _TCHAR* argv[])
{
int intersectionSize = 0;
vector<int> set1, set2;
createSets( set1, set2 );
Timer timer;
for ( int i = 0; i < 1000; i++ )
{
intersectionSize = runIntersectionTest(set1, set2);
}
timer.Stop();
cout << "Found the intersection of " << intersectionSize << " values (using unordered_map) 1000 times, in " << timer.GetMilliseconds() << "ms" << endl;
timer.Reset();
for ( int i = 0; i < 1000; i++ )
{
intersectionSize = runSetIntersection(set1,set2);
}
timer.Stop();
cout << "Found the intersection of " << intersectionSize << " values (using set_intersection) 1000 times, in " << timer.GetMilliseconds() << "ms" << endl;
timer.Reset();
for ( int i = 0; i < 1000; i++ )
{
intersectionSize = runIntersectionTest2(set1,set2);
}
timer.Stop();
cout << "Found the intersection of " << intersectionSize << " values (using unordered_set) 1000 times, in " << timer.GetMilliseconds() << "ms" << endl;
getchar();
return 0;
}
Codice C#:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
namespace DictionaryPerformance
{
class Program
{
static void Main(string[] args)
{
List<int> set1 = new List<int>(100000);
List<int> set2 = new List<int>(1000);
// Create 100,000 values for set1
for (int i = 0; i < 100000; i++)
{
int value = 1000000000 + i;
set1.Add(value);
}
Random random = new Random(DateTime.Now.Millisecond);
// Create 1,000 values for set2
for (int i = 0; i < 1000; i++)
{
int value = 1000000000 + (random.Next() % 200000 + 1);
set2.Add(value);
}
long start = System.Diagnostics.Stopwatch.GetTimestamp();
for (int i = 0; i < 1000; i++)
{
runIntersectionTest(set1,set2);
}
long duration = System.Diagnostics.Stopwatch.GetTimestamp() - start;
Console.WriteLine(String.Format("Found the intersection 1000 times, in {0} ms", ((float) duration * 1000.0f) / System.Diagnostics.Stopwatch.Frequency));
Console.ReadKey();
}
static int runIntersectionTest(List<int> set1, List<int> set2)
{
Dictionary<int,int> theMap = new Dictionary<int,int>(100000);
// 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 ) )
{
theMap[value] = 2;
intersectionSize++;
}
}
return intersectionSize;
}
}
}
Codice C++:
// MapPerformance.cpp : Defines the entry point for the console application.
//
#include "stdafx.h"
#include <hash_map>
#include <vector>
#include <iostream>
#include <time.h>
#include <algorithm>
#include <set>
#include <boost\unordered\unordered_map.hpp>
#include "timer.h"
using namespace std;
using namespace stdext;
using namespace boost;
int runIntersectionTest(vector<int> set1, vector<int> set2)
{
// hash_map<int,int> theMap;
// map<int,int> theMap;
unordered_map<int,int> theMap;
// 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;
unordered_map<int,int>::iterator foundValue = theMap.find(value);
if ( foundValue != theMap.end() )
{
theMap[value] = 2;
intersectionSize++;
}
}
return intersectionSize;
}
int runSetIntersection(set<int> set1, set<int> set2)
{
set<int> intersection;
set_intersection(set1.begin(),set1.end(), set2.begin(), set2.end(), inserter(intersection, intersection.end()));
return intersection.size();
}
int _tmain(int argc, _TCHAR* argv[])
{
srand ( time(NULL) );
vector<int> set1;
vector<int> set2;
set1.reserve(10000);
set2.reserve(1000);
// 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);
}
Timer timer;
for ( int i = 0; i < 1000; i++ )
{
runIntersectionTest(set1, set2);
}
timer.Stop();
cout << "Found the intersection (using unordered_map) 1000 times, in " << timer.GetMilliseconds() << "ms" << endl;
set<int> set21;
set<int> set22;
// Create 100,000 values for set1
for ( int i = 0; i < 100000; i++ )
{
int value = 1000000000 + i;
set21.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;
set22.insert(value);
}
timer.Reset();
for ( int i = 0; i < 1000; i++ )
{
runSetIntersection(set21,set22);
}
timer.Stop();
cout << "Found the intersection (using set_intersection) 1000 times, in " << timer.GetMilliseconds() << "ms" << endl;
getchar();
return 0;
}
Ok, ecco l'ultima, con alcune modifiche:
- I set C++ ora sono configurati correttamente in modo che abbiano un'intersezione del 50% (come C#)
- Il set1 è mescolato quindi non è ordinato, il set2 non era già ordinato
- L'implementazione set_intersection ora utilizza i vettori e li ordina prima
Risultati C++ (versione x64):
Found the intersection of 503 values (using unordered_map) 1000 times, in 35131.1ms
Found the intersection of 494 values (using set_intersection) 1000 times, in 10317ms
Quindi è 2 volte più lento di C#.@Jalf:Stai ottenendo dei numeri piuttosto veloci, c'è qualcosa che sbaglio qui?
Codice C++:
// MapPerformance.cpp : Defines the entry point for the console application.
//
#include "stdafx.h"
#include <hash_map>
#include <vector>
#include <iostream>
#include <time.h>
#include <algorithm>
#include <set>
#include <boost\unordered\unordered_map.hpp>
#include "timer.h"
using namespace std;
using namespace stdext;
using namespace boost;
int runIntersectionTest(const vector<int>& set1, const vector<int>& set2)
{
// hash_map<int,int> theMap;
// map<int,int> theMap;
unordered_map<int,int> theMap;
vector<int>::const_iterator set1_end = set1.end();
// Now intersect the two sets by populating the map
for ( vector<int>::const_iterator iterator = set1.begin(); iterator != set1_end; ++iterator )
{
int value = *iterator;
theMap[value] = 1;
}
int intersectionSize = 0;
vector<int>::const_iterator set2_end = set2.end();
for ( vector<int>::const_iterator iterator = set2.begin(); iterator != set2_end; ++iterator )
{
int value = *iterator;
unordered_map<int,int>::iterator foundValue = theMap.find(value);
if ( foundValue != theMap.end() )
{
theMap[value] = 2;
intersectionSize++;
}
}
return intersectionSize;
}
int runSetIntersection(const vector<int> set1_unsorted, const vector<int> set2_unsorted)
{
// Create two vectors
std::vector<int> set1(set1_unsorted.size());
std::vector<int> set2(set2_unsorted.size());
// Copy the unsorted data into them
std::copy(set1_unsorted.begin(), set1_unsorted.end(), set1.begin());
std::copy(set2_unsorted.begin(), set2_unsorted.end(), set2.begin());
// Sort the data
sort(set1.begin(),set1.end());
sort(set2.begin(),set2.end());
vector<int> intersection;
intersection.reserve(1000);
set_intersection(set1.begin(),set1.end(), set2.begin(), set2.end(), inserter(intersection, intersection.end()));
return intersection.size();
}
void createSets( vector<int>& set1, vector<int>& set2 )
{
srand ( time(NULL) );
set1.reserve(100000);
set2.reserve(1000);
// Create 100,000 values for set1
for ( int i = 0; i < 100000; i++ )
{
int value = 1000000000 + i;
set1.push_back(value);
}
// Try to get half of our values intersecting
float ratio = 200000.0f / RAND_MAX;
// Create 1,000 values for set2
for ( int i = 0; i < 1000; i++ )
{
int random = rand() * ratio + 1;
int value = 1000000000 + random;
set2.push_back(value);
}
// Make sure set1 is in random order (not sorted)
random_shuffle(set1.begin(),set1.end());
}
int _tmain(int argc, _TCHAR* argv[])
{
int intersectionSize = 0;
vector<int> set1, set2;
createSets( set1, set2 );
Timer timer;
for ( int i = 0; i < 1000; i++ )
{
intersectionSize = runIntersectionTest(set1, set2);
}
timer.Stop();
cout << "Found the intersection of " << intersectionSize << " values (using unordered_map) 1000 times, in " << timer.GetMilliseconds() << "ms" << endl;
timer.Reset();
for ( int i = 0; i < 1000; i++ )
{
intersectionSize = runSetIntersection(set1,set2);
}
timer.Stop();
cout << "Found the intersection of " << intersectionSize << " values (using set_intersection) 1000 times, in " << timer.GetMilliseconds() << "ms" << endl;
getchar();
return 0;
}
Soluzione
Ci sono diversi problemi con il test.
In primo luogo, non si è di prova di intersezione, ma "creare una coppia di array, riempirli con numeri casuali, e quindi eseguire set di intersezione". Si dovrebbe cronometrare solo la parte del codice si sta effettivamente interessa. Anche se si sta andando a voler fare quelle cose, che non dovrebbero essere una valutazione comparativa qui. Misurare una cosa alla volta, per ridurre l'incertezza. Se si desidera che l'implementazione C ++ per eseguire meglio, è necessario prima di sapere quale parte di esso è più lento del previsto. Il che significa che si deve separare il codice di configurazione da test di intersezione.
In secondo luogo, è necessario eseguire il test di un gran numero di volte per prendere i possibili effetti di caching e altre incertezze in considerazione. (E probabilmente un'uscita tempo totale per, diciamo, 1000 corse, piuttosto che un tempo individuale per ciascuno. In questo modo si riduce l'incertezza dal timer che potrebbe avere risoluzione limitata e riportare i risultati non accurati quando utilizzati nella gamma 0-20ms.
Inoltre, per quanto posso leggere la documentazione, l'ingresso set_intersection deve essere ordinato, che set2 non sarà. Un non sembra esserci alcuna ragione per usare unordered_map
, quando unordered_set
sarebbe un match molto meglio per quello che stai facendo.
A proposito di essere necessario il codice di setup, notare che probabilmente non hanno bisogno per popolare i vettori al fine di eseguire l'incrocio. Sia la propria implementazione e set_intersection
lavoro su iteratori già, quindi si può semplicemente passare loro una coppia di iteratori alle strutture dati gli ingressi sono già.
Alcuni commenti più specifici sul tuo codice:
- Usa
++iterator
invece diiterator++
- piuttosto che chiamare vector.end () ad ogni iterazione del ciclo, chiamare una volta e memorizzare nella cache il risultato
- esperimento con l'utilizzo di vettori ordinati vs std :: set vs
malloc
(nonset_difference
)
Modifica
Non ho provato la versione C #, quindi non posso confrontare i numeri correttamente, ma qui è la mia prova modificato. Ciascuno è gestita 1000 volte, su un Core 2 Quad 2.5GHz con 4GB di RAM:
std::set_intersection on std::set: 2606ms
std::set_intersection on tr1::unordered_set: 1014ms
std::set_intersection on sorted vectors: 171ms
std::set_intersection on unsorted vectors: 10140ms
L'ultimo è un po 'ingiusto, perché deve sia copiare e ordinare i vettori. Idealmente, solo il tipo dovrebbe essere parte del benchmark. Ho cercato di creare una versione che utilizza una matrice di 1000 vettori indifferenziati (quindi woudln't devo copiare i dati non ordinati in ogni iterazione), ma la prestazione è stata circa la stessa, o un po 'peggio, perché ciò comporterebbe cache miss costanti , così ho ritornato di nuovo a questa versione
E il mio codice:
#define _SECURE_SCL 0
#include <ctime>
#include <vector>
#include <set>
#include <iostream>
#include <algorithm>
#include <unordered_set>
#include <windows.h>
template <typename T, typename OutIter>
void stl_intersect(const T& set1, const T& set2, OutIter out){
std::set_intersection(set1.begin(), set1.end(), set2.begin(), set2.end(), out);
}
template <typename T, typename OutIter>
void sort_stl_intersect(T& set1, T& set2, OutIter out){
std::sort(set1.begin(), set1.end());
std::sort(set2.begin(), set2.end());
std::set_intersection(set1.begin(), set1.end(), set2.begin(), set2.end(), out);
}
template <typename T>
void init_sorted_vec(T first, T last){
for ( T cur = first; cur != last; ++cur)
{
int i = cur - first;
int value = 1000000000 + i;
*cur = value;
}
}
template <typename T>
void init_unsorted_vec(T first, T last){
for ( T cur = first; cur != last; ++cur)
{
int i = rand() % 200000 + 1;
i *= 10;
int value = 1000000000 + i;
*cur = value;
}
}
struct resize_and_shuffle {
resize_and_shuffle(int size) : size(size) {}
void operator()(std::vector<int>& vec){
vec.resize(size);
}
int size;
};
int main()
{
srand ( time(NULL) );
std::vector<int> out(100000);
std::vector<int> sortedvec1(100000);
std::vector<int> sortedvec2(1000);
init_sorted_vec(sortedvec1.begin(), sortedvec1.end());
init_unsorted_vec(sortedvec2.begin(), sortedvec2.end());
std::sort(sortedvec2.begin(), sortedvec2.end());
std::vector<int> unsortedvec1(sortedvec1.begin(), sortedvec1.end());
std::vector<int> unsortedvec2(sortedvec2.begin(), sortedvec2.end());
std::random_shuffle(unsortedvec1.begin(), unsortedvec1.end());
std::random_shuffle(unsortedvec2.begin(), unsortedvec2.end());
std::vector<int> vecs1[1000];
std::vector<int> vecs2[1000];
std::fill(vecs1, vecs1 + 1000, unsortedvec1);
std::fill(vecs2, vecs2 + 1000, unsortedvec2);
std::set<int> set1(sortedvec1.begin(), sortedvec1.end());
std::set<int> set2(sortedvec2.begin(), sortedvec2.end());
std::tr1::unordered_set<int> uset1(sortedvec1.begin(), sortedvec1.end());
std::tr1::unordered_set<int> uset2(sortedvec2.begin(), sortedvec2.end());
DWORD start, stop;
DWORD delta[4];
start = GetTickCount();
for (int i = 0; i < 1000; ++i){
stl_intersect(set1, set2, out.begin());
}
stop = GetTickCount();
delta[0] = stop - start;
start = GetTickCount();
for (int i = 0; i < 1000; ++i){
stl_intersect(uset1, uset2, out.begin());
}
stop = GetTickCount();
delta[1] = stop - start;
start = GetTickCount();
for (int i = 0; i < 1000; ++i){
stl_intersect(sortedvec1, sortedvec2, out.begin());
}
stop = GetTickCount();
delta[2] = stop - start;
start = GetTickCount();
for (int i = 0; i < 1000; ++i){
sort_stl_intersect(vecs1[i], vecs1[i], out.begin());
}
stop = GetTickCount();
delta[3] = stop - start;
std::cout << "std::set_intersection on std::set: " << delta[0] << "ms\n";
std::cout << "std::set_intersection on tr1::unordered_set: " << delta[1] << "ms\n";
std::cout << "std::set_intersection on sorted vectors: " << delta[2] << "ms\n";
std::cout << "std::set_intersection on unsorted vectors: " << delta[3] << "ms\n";
return 0;
}
Non c'è alcun motivo per cui C ++ deve essere sempre più veloce di C #. C # ha alcuni vantaggi chiave che richiedono molta cura di competere con in C ++.
Il primario mi viene in mente è che gli stanziamenti dinamici sono ridicolmente basso costo in NET-terre. Ogni volta che un vettore C ++, impostare o unordered_set (o qualsiasi altro contenitore) deve ridimensionare o espandere, è un'operazione molto costosa std::sort
. NET, un'allocazione mucchio è poco più di aggiungendo un offset di un puntatore.
Quindi, se si desidera la versione C ++ di competere, probabilmente dovuto risolvere quel, consentendo ai contenitori per ridimensionare, senza dover eseguire le allocazioni heap effettivi, probabilmente utilizzando allocatori personalizzate per i contenitori (forse boost :: piscina potrebbe essere una buona scommessa, oppure si può provare a rotolare il proprio)
Un altro problema è che <=> funziona solo su input ordinato, e al fine di riprodurre i risultati di prove che implicano una specie, dobbiamo fare una nuova copia dei dati non ordinati in ogni iterazione, che è costosa (anche se ancora, utilizzando allocatori personalizzati aiuterà molto). Non so quale forma prende l'input, ma è possibile che si può ordinare il vostro input direttamente, senza copiarlo, e quindi eseguire direttamente <=> su questo. (Che sarebbe facile da fare se l'input è una matrice o un contenitore STL almeno.)
Uno dei vantaggi principali della STL è che è così flessibile, può funzionare su praticamente qualsiasi sequenza di input. In C #, è praticamente necessario per copiare l'ingresso a una lista o un dizionario o qualcosa del genere, main C ++, si potrebbe essere in grado di cavarsela con l'esecuzione <=> e <=> sull'ingresso prima.
Infine, naturalmente, provare a eseguire il codice attraverso un profiler e vedere esattamente dove il tempo viene speso. Si potrebbe anche voler provare a eseguire il codice tramite GCC, invece. E 'mia impressione che le prestazioni STL in MSVC è a volte un po' eccentrico. Potrebbe valere la pena testare sotto un altro compilatore solo per vedere se si ottiene tempi simili lì.
Infine, si potrebbe trovare questi post del blog rilevanti per le prestazioni di C ++ vs C #: http://blogs.msdn.com/ricom/archive/2005 /05/10/416151.aspx
Il morale di questi è in sostanza che sì, è possibile ottenere migliori prestazioni in C ++, ma è una sorprendente quantità di lavoro.
Altri suggerimenti
Un problema che vedo subito è che si sta passando i set in C ++ per valore e non per riferimento const. Quindi stai copiando loro ogni volta che li passa in giro!
Inoltre, non vorrei usare un set per il target di set_intersection
. Vorrei usare qualcosa come
int runSetIntersection(const set<int>& set1, const set<int>& set2)
{
vector<int> intersection;
intersection.reserve(10000) // or whatever the max is
set_intersection(set1.begin(),set1.end(), set2.begin(), set2.end(), back_inserter(intersection));
return intersection.size();
}
Questo codice, tuttavia, alloca ancora all'interno della funzione. Ancora più veloce sarebbe
int runSetIntersection(const set<int>& set1, const set<int>& set2, vector<int>& scratch)
{
scratch.reserve(10000) // or whatever the max is
set_intersection(set1.begin(),set1.end(), set2.begin(), set2.end(), back_inserter(scratch));
return scratch.size();
}
E poi allocare zero prima di avviare il timer.
Anche se, se si sta solo cercando la dimensione, una mano-scritto per ciclo, in combinazione con il set :: trovare potrebbe dare risultati ancora migliori.
Con questo ...
vector<int> set1(10000);
vector<int> set2(1000);
... per ottenere vettori di non-zero dimensione iniziale. Quindi non utilizzare push_back, ma solo aggiornare direttamente i valori.
Vorrei cambiare il C ++ "runIntersectionTest" a prendere i riferimenti const ai contenitori piuttosto che copiarle-costruiti su ogni chiamata. (Il codice C # sarà utilizzando rif.)
Può anche essere utile guardare la spinta Disgiunte Set contenitore , che è appositamente ottimizzato per alcuni tipi di grandi operazioni di set.
Funziona trattando un gruppo di set, come i sindacati dei diversi insiemi disgiunti, rendendo possibile la costruzione di altri set, come incroci o unioni molto a buon mercato, una volta che la prima serie di insiemi disgiunti è costruito. Se ci si aspetta di fare un sacco di operazioni di set in set che non cambia molto, si può probabilmente aspetto che questo sia molto veloce. Se, d'altra parte, si utilizzerà ogni set una sola volta e buttarlo via, probabilmente non andando a fare troppo.
In ogni caso, faresti un favore ad almeno sperimentare con questo per vedere se ti dà alcun urto nel vostro caso specifico.
A proposito, se si dispone di grandi insiemi ordinati std :: set_intersection non è l'algoritmo più veloce. std :: set_intersection richiede fino a 2 * (m + n) -1 confronti ma algoritmi come quella da Baeza-Yates può essere più veloce. Per i piccoli m, Baeza-Yates è O (m * log (n)), mentre per n = alpha * m è O (n). L'idea di base è quella di fare una sorta di 2 vie ricerca binaria.
http://citeseerx.ist .psu.edu / viewdoc / scaricare? doi = 10.1.1.91.7899 & rep = rep1 & type = pdf
Analisi sperimentale di un algoritmo di intersezione veloce per Ordinati sequenze Ricardo Baeza-Yates e Alejandro Salinger
o
R. Baeza-Yates. Un algoritmo di intersezione Set veloce per Ordinati sequenze. Nel Atti del Simposio 15th Annual su Combinatorial Pattern Matching (CPM 2004), Springer LNCS 3109, pp 400-408, Istanbul, Turchia, luglio 2004.
Di seguito è una spiegazione e un'implementazione di Erik Frey dove mostra risultati significativamente più veloce di std :: set_intersection con una sonda binario. Non ho ancora provato il suo codice.
http://fawx.com/
- Selezionare l'elemento mediano, A, nel insieme più piccolo.
- Ricerca per il suo elemento di inserimento-posizione, B, in il set più grande.
- Se A e B sono uguali, aggiungere l'elemento al risultato.
- Ripetere i passaggi 1-4 a sottoinsiemi non vuoti su entrambi i lati degli elementi A e B.
;
/*
* baeza_intersect
*/
template< template class Probe,
class RandomAccessIterator, class OutputIterator>
void baeza_intersect(RandomAccessIterator begin1, RandomAccessIterator end1,
RandomAccessIterator begin2, RandomAccessIterator end2,
OutputIterator out)
{
RandomAccessIterator probe1, probe2;
if ( (end1 - begin1) < ( end2 - begin2 ) )
{
if ( begin1 == end1 )
return;
probe1 = begin1 + ( ( end1 - begin1 ) >> 1 );
probe2 = lower_bound< Probe >( begin2, end2, *probe1 );
baeza_intersect< Probe >(begin1, probe1, begin2, probe2, out); // intersect left
if (! (probe2 == end2 || *probe1 < *probe2 ))
*out++ = *probe2++;
baeza_intersect< Probe >(++probe1, end1, probe2, end2, out); // intersect right
}
else
{
if ( begin2 == end2 )
return;
probe2 = begin2 + ( ( end2 - begin2 ) >> 1 );
probe1 = lower_bound< Probe >( begin1, end1, *probe2 );
baeza_intersect< Probe >(begin1, probe1, begin2, probe2, out); // intersect left
if (! (probe1 == end1 || *probe2 < *probe1 ))
*out++ = *probe1++;
baeza_intersect< Probe >(probe1, end1, ++probe2, end2, out); // intersect right
}
}
/*
* with a comparator
*/
template< template class Probe,
class RandomAccessIterator, class OutputIterator, class Comparator >
void baeza_intersect(RandomAccessIterator begin1, RandomAccessIterator end1,
RandomAccessIterator begin2, RandomAccessIterator end2,
OutputIterator out, Comparator cmp)
{
RandomAccessIterator probe1, probe2;
if ( (end1 - begin1) < ( end2 - begin2 ) )
{
if ( begin1 == end1 )
return;
probe1 = begin1 + ( ( end1 - begin1 ) >> 1 );
probe2 = lower_bound< Probe >( begin2, end2, *probe1, cmp );
baeza_intersect< Probe >(begin1, probe1, begin2, probe2, out, cmp); // intersect left
if (! (probe2 == end2 || cmp( *probe1, *probe2 ) ))
*out++ = *probe2++;
baeza_intersect< Probe >(++probe1, end1, probe2, end2, out, cmp); // intersect right
}
else
{
if ( begin2 == end2 )
return;
probe2 = begin2 + ( ( end2 - begin2 ) >> 1 );
probe1 = lower_bound< Probe >( begin1, end1, *probe2, cmp );
baeza_intersect< Probe >(begin1, probe1, begin2, probe2, out, cmp); // intersect left
if (! (probe1 == end1 || cmp( *probe2, *probe1 ) ))
*out++ = *probe1++;
baeza_intersect< Probe >(probe1, end1, ++probe2, end2, out, cmp); // intersect right
}
}
// probe.hpp
/ **
* Sonda binario: scegliere l'elemento successivo scegliendo il punto a metà strada tra la bassa e alta
* /
template
struct binary_probe
{
operatore RandomAccessIterator () (RandomAccessIterator inizio, fine RandomAccessIterator, const T & value)
{
return iniziare + ((fine - inizio) >> 1);
}
};
/ **
* Inferiore limite: come inferiore limite di STL, ma con diversi tipi di sondare
* Nota l'aspetto del modello di parametro di rara template!
* /
template
RandomAccessIterator lower_bound (RandomAccessIterator inizio, fine RandomAccessIterator, const T & value)
{
RandomAccessIterator fossa;
Probe pfunc; // Sonda-funtore (vuole ottenere func'd up)
mentre (inizio
/ *
* Questa volta con un comparatore!
* /
template
RandomAccessIterator lower_bound (RandomAccessIterator inizio, fine RandomAccessIterator, const T & valore, comparatore CMP)
{
RandomAccessIterator fossa;
Probe pfunc;
mentre (inizio
Dal momento che si sta utilizzando Visual Studio si dovrebbe verificare se si è _SECURE_SCL
impostato a 1 (di solito se non lo avete impostato in modo esplicito che sarà 1). Se è impostato tutto STL-codice sarà gamma-controllato, anche nelle release-build. Tipicamente rallentando codice da 10-15%.
Sembra Microsoft non era a conoscenza che, per esempio std :: vector ha già un'interfaccia se si desidera che la gamma di controllo: std :: vector :: a ()
(Sorry, ha dovuto farlo fuori il petto).
In ogni caso l'inefficienza principale è che si sta copiando i contenitori invece di passaggio per valore. Utilizzare i riferimenti per (cercare di) confrontare le mele e le mele, invece di mele e banane.
So che la soluzione sta lavorando bene, ma avete provato utilizzando le implementazioni STL:
Potrebbe essere ottimizzato per la piattaforma richiesta già, quindi mi piacerebbe dare un colpo
sono C ++ flag di ottimizzazione accesi?
Ok, dopo molti feedback ho aggiornato la domanda originale diverse volte:
- I test vengono ora eseguiti 1.000 volte ciascuno
- Il codice C# ora usa un timer con risoluzione più elevata
- Le strutture dati sono ora popolate PRIMA dei test
Il risultato finora è che C# è ancora ~ 5 volte più veloce di C++.
Grazie a tutti per le vostre idee/suggerimenti.
Aggiornamento:
ho modificato il codice set_intersection di utilizzare i vettori, e per ordinare loro (invece di utilizzare la classe insieme ordinato), e la sua molto più veloce:
Found the intersection of 319 values (using unordered_map) 1000 times, in 22187.5ms
Found the intersection of 315 values (using set_intersection) 1000 times, in 2401.62ms
Tenete a mente:. Il set più grande è creato allineati, quindi l'ordinamento potrebbe non prendere molto tempo in questo esempio
Codice C ++:
// MapPerformance.cpp : Defines the entry point for the console application.
//
#include "stdafx.h"
#include <hash_map>
#include <vector>
#include <iostream>
#include <time.h>
#include <algorithm>
#include <set>
#include <boost\unordered\unordered_map.hpp>
#include "timer.h"
using namespace std;
using namespace stdext;
using namespace boost;
int runIntersectionTest(vector<int> set1, vector<int> set2)
{
// hash_map<int,int> theMap;
// map<int,int> theMap;
unordered_map<int,int> theMap;
// 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;
unordered_map<int,int>::iterator foundValue = theMap.find(value);
if ( foundValue != theMap.end() )
{
theMap[value] = 2;
intersectionSize++;
}
}
return intersectionSize;
}
int runSetIntersection(vector<int> set1, vector<int> set2)
{
sort(set1.begin(),set1.end());
sort(set2.begin(),set2.end());
set<int> intersection;
set_intersection(set1.begin(),set1.end(), set2.begin(), set2.end(), inserter(intersection, intersection.end()));
return intersection.size();
}
int _tmain(int argc, _TCHAR* argv[])
{
srand ( time(NULL) );
vector<int> set1;
vector<int> set2;
set1.reserve(10000);
set2.reserve(1000);
// 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);
}
int intersectionSize = 0;
Timer timer;
for ( int i = 0; i < 1000; i++ )
{
intersectionSize = runIntersectionTest(set1, set2);
}
timer.Stop();
cout << "Found the intersection of " << intersectionSize << " values (using unordered_map) 1000 times, in " << timer.GetMilliseconds() << "ms" << endl;
timer.Reset();
for ( int i = 0; i < 1000; i++ )
{
intersectionSize = runSetIntersection(set1,set2);
}
timer.Stop();
cout << "Found the intersection of " << intersectionSize << " values (using set_intersection) 1000 times, in " << timer.GetMilliseconds() << "ms" << endl;
getchar();
return 0;
}
si sta ancora passando i vettori per valore. Che sarebbe ok se non li stavano copiando pure.
inseritore non mettendo i valori alla fine del vettore dov'è veloce. Esso ha soltanto che il primo inserto dopo che è inserito il valore all'inizio dell'array (dove fine utilizzato per puntare).
dove alzando il valore due volte nella versione hash carta, durante l'aggiornamento del valore. Perché questo evento valore in corso di aggiornamento?
eseguire questo codice e pubblica i tuoi tempi.
// MapPerformance.cpp : Defines the entry point for the console application.
//
#include "stdafx.h"
#include <hash_map>
#include <vector>
#include <iostream>
#include <time.h>
#include <algorithm>
#include <set>
#include <boost\unordered\unordered_set.hpp>
#include "timer.h"
using namespace std;
using namespace stdext;
using namespace boost;
int runIntersectionTest(const vector<int>& set1, const vector<int>& set2)
{
// hash_map<int,int> theMap;
// map<int,int> theMap;
unordered_set<int> theSet;
theSet.insert( set1.begin(), set2.end() );
int intersectionSize = 0;
vector<int>::const_iterator set2_end = set2.end();
for ( vector<int>::const_iterator iterator = set2.begin(); iterator != set2_end; ++iterator )
{
if ( theSet.find(*iterator) != theSet.end() )
{
intersectionSize++;
}
}
return intersectionSize;
}
int runSetIntersection( vector<int> set1, vector<int> set2)
{
// Sort the data
sort(set1.begin(),set1.end());
sort(set2.begin(),set2.end());
vector<int> intersection;
intersection.reserve(1000);
set_intersection(set1.begin(),set1.end(), set2.begin(), set2.end(), back_inserter(intersection));
return intersection.size();
}
void createSets( vector<int>& set1, vector<int>& set2 )
{
srand ( time(NULL) );
set1.reserve(100000);
set2.reserve(1000);
// Create 100,000 values for set1
for ( int i = 0; i < 100000; i++ )
{
int value = 1000000000 + i;
set1.push_back(value);
}
// Try to get half of our values intersecting
float ratio = 200000.0f / RAND_MAX;
// Create 1,000 values for set2
for ( int i = 0; i < 1000; i++ )
{
int random = rand() * ratio + 1;
int value = 1000000000 + random;
set2.push_back(value);
}
// Make sure set1 is in random order (not sorted)
random_shuffle(set1.begin(),set1.end());
}
int _tmain(int argc, _TCHAR* argv[])
{
int intersectionSize = 0;
vector<int> set1, set2;
createSets( set1, set2 );
Timer timer;
for ( int i = 0; i < 1000; i++ )
{
intersectionSize = runIntersectionTest(set1, set2);
}
timer.Stop();
cout << "Found the intersection of " << intersectionSize << " values (using unordered_map) 1000 times, in " << timer.GetMilliseconds() << "ms" << endl;
timer.Reset();
for ( int i = 0; i < 1000; i++ )
{
intersectionSize = runSetIntersection(set1,set2);
}
timer.Stop();
cout << "Found the intersection of " << intersectionSize << " values (using set_intersection) 1000 times, in " << timer.GetMilliseconds() << "ms" << endl;
getchar();
return 0;
}
Ultimo punto di riferimento:
Found the intersection of 504 values (using unordered_map) 1000 times, in 28827.6ms
Found the intersection of 495 values (using set_intersection) 1000 times, in 9817.69ms
Found the intersection of 504 values (using unordered_set) 1000 times, in 24769.1ms
Credo che la 504 - 495 differenza accade perché ci sono un paio valori babbeo
.Code:
// MapPerformance.cpp : Defines the entry point for the console application.
//
#include "stdafx.h"
#include <hash_map>
#include <vector>
#include <iostream>
#include <time.h>
#include <algorithm>
#include <set>
#include <unordered_set>
#include <boost\unordered\unordered_map.hpp>
#include "timer.h"
using namespace std;
using namespace stdext;
using namespace boost;
using namespace tr1;
int runIntersectionTest2(const vector<int>& set1, const vector<int>& set2)
{
// hash_map<int,int> theMap;
// map<int,int> theMap;
unordered_set<int> theSet;
theSet.insert( set1.begin(), set1.end() );
int intersectionSize = 0;
vector<int>::const_iterator set2_end = set2.end();
for ( vector<int>::const_iterator iterator = set2.begin(); iterator != set2_end; ++iterator )
{
if ( theSet.find(*iterator) != theSet.end() )
{
intersectionSize++;
}
}
return intersectionSize;
}
int runIntersectionTest(const vector<int>& set1, const vector<int>& set2)
{
// hash_map<int,int> theMap;
// map<int,int> theMap;
unordered_map<int,int> theMap;
vector<int>::const_iterator set1_end = set1.end();
// Now intersect the two sets by populating the map
for ( vector<int>::const_iterator iterator = set1.begin(); iterator != set1_end; ++iterator )
{
int value = *iterator;
theMap[value] = 1;
}
int intersectionSize = 0;
vector<int>::const_iterator set2_end = set2.end();
for ( vector<int>::const_iterator iterator = set2.begin(); iterator != set2_end; ++iterator )
{
int value = *iterator;
unordered_map<int,int>::iterator foundValue = theMap.find(value);
if ( foundValue != theMap.end() )
{
theMap[value] = 2;
intersectionSize++;
}
}
return intersectionSize;
}
int runSetIntersection(const vector<int>& set1_unsorted, const vector<int>& set2_unsorted)
{
// Create two vectors
std::vector<int> set1(set1_unsorted.size());
std::vector<int> set2(set2_unsorted.size());
// Copy the unsorted data into them
std::copy(set1_unsorted.begin(), set1_unsorted.end(), set1.begin());
std::copy(set2_unsorted.begin(), set2_unsorted.end(), set2.begin());
// Sort the data
sort(set1.begin(),set1.end());
sort(set2.begin(),set2.end());
vector<int> intersection;
intersection.reserve(1000);
set_intersection(set1.begin(),set1.end(), set2.begin(), set2.end(), back_inserter(intersection));
return intersection.size();
}
void createSets( vector<int>& set1, vector<int>& set2 )
{
srand ( time(NULL) );
set1.reserve(100000);
set2.reserve(1000);
// Create 100,000 values for set1
for ( int i = 0; i < 100000; i++ )
{
int value = 1000000000 + i;
set1.push_back(value);
}
// Try to get half of our values intersecting
float ratio = 200000.0f / RAND_MAX;
// Create 1,000 values for set2
for ( int i = 0; i < 1000; i++ )
{
int random = rand() * ratio + 1;
int value = 1000000000 + random;
set2.push_back(value);
}
// Make sure set1 is in random order (not sorted)
random_shuffle(set1.begin(),set1.end());
}
int _tmain(int argc, _TCHAR* argv[])
{
int intersectionSize = 0;
vector<int> set1, set2;
createSets( set1, set2 );
Timer timer;
for ( int i = 0; i < 1000; i++ )
{
intersectionSize = runIntersectionTest(set1, set2);
}
timer.Stop();
cout << "Found the intersection of " << intersectionSize << " values (using unordered_map) 1000 times, in " << timer.GetMilliseconds() << "ms" << endl;
timer.Reset();
for ( int i = 0; i < 1000; i++ )
{
intersectionSize = runSetIntersection(set1,set2);
}
timer.Stop();
cout << "Found the intersection of " << intersectionSize << " values (using set_intersection) 1000 times, in " << timer.GetMilliseconds() << "ms" << endl;
timer.Reset();
for ( int i = 0; i < 1000; i++ )
{
intersectionSize = runIntersectionTest2(set1,set2);
}
timer.Stop();
cout << "Found the intersection of " << intersectionSize << " values (using unordered_set) 1000 times, in " << timer.GetMilliseconds() << "ms" << endl;
getchar();
return 0;
}