Быстрое пересечение множеств: C++ vs C#
на моей машине (Quad core, 8GB ram), работающей под управлением Vista x64 Business, с Visual Studio 2008 SP1, я пытаюсь очень быстро пересечь два набора чисел.
я реализовал два подхода на C++ и один на C#. Подход c# пока быстрее, я хотел бы улучшить подход C++, чтобы он был быстрее, чем C#, что, как я ожидаю, может сделать C++.
вот вывод C#: (Release build)
Found the intersection 1000 times, in 4741.407 ms
вот начальный вывод C++ для двух разных подходы (выпуск x64 build):
Found the intersection (using unordered_map) 1000 times, in 21580.7ms
Found the intersection (using set_intersection) 1000 times, in 22366.6ms
вот последний вывод C++ для трех подходов (выпуск x64 build):
последний тест:
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
Итак, подход set_intersection теперь примерно на 2x медленнее, чем C#, но на 2x быстрее, чем исходные подходы c++.
последний 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 <boostunorderedunordered_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;
}
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;
}
}
}
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 <boostunorderedunordered_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, вот последний, с некоторыми изменения:
- наборы C++ теперь правильно настроены, поэтому они имеют пересечение 50% (например, C#)
- Set1 перемешивается, поэтому его не сортируется, set2 уже не сортируется
- реализация set_intersection теперь использует векторы, и сортирует их первый
C++ (Release, 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
таким образом, его 2x медленнее, чем C#. @Jalf: вы получаете довольно быстрые цифры, есть ли что-то, что я делаю неправильно здесь?
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 <boostunorderedunordered_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;
}
13 ответов
есть несколько проблем с вашим тестом.
во-первых, вы не тестируете пересечение множества, а "создаете пару массивов, заполняете их случайными числами, а затем выполняете пересечение множества". Вы должны только часть кода Вы на самом деле заинтересованы. Даже если вы собираетесь сделать эти вещи, они не должны быть сопоставлены здесь. Измеряйте одну вещь за раз, чтобы уменьшить неопределенность. Если вы хотите, чтобы ваша реализация C++ работала лучше, вы сначала нужно знать, какая часть медленнее, чем ожидалось. Это означает, что вам нужно отделить код установки от теста пересечения.
во-вторых, вы должны запустить тест большое количество раз, чтобы учесть возможные эффекты кэширования и другие неопределенности. (И, вероятно, выведите одно общее время, скажем, для 1000 запусков, а не отдельное время для каждого. Таким образом, вы уменьшаете неопределенность от таймера, который может иметь ограниченное разрешение и сообщать о неточных результатах при использовании в в 0-20мс диапазон.
далее, насколько я могу прочитать из документов, вход в set_intersection должен быть отсортирован, чего set2 не будет. Кажется, нет причин использовать unordered_map
, когда unordered_set
было бы гораздо лучше подходит для того, что вы делаете.
о необходимости кода установки обратите внимание, что вы, вероятно,не необходимо заполнить векторы для запуска пересечения. Как ваша собственная реализация, так и set_intersection
работа над итераторами уже, поэтому вы можете просто передать им пару итераторов структуры данных данные в уже.
несколько более конкретных комментариев к вашему коду:
- использовать
++iterator
вместоiterator++
- вместо вызова вектора.end () на каждой итерации цикла вызовите его один раз и кэшируйте результат
- эксперимент с использованием отсортированных векторов vs std:: set vs
unordered_set
(неunordered_map
)
Edit:
I не пробовал вашу версию C#, поэтому я не могу сравнить цифры должным образом, но вот мой модифицированный тест. Каждый запускается 1000 раз, на ядре 2 Quad 2.5 GHz с 4GB 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
последний немного несправедлив, потому что он должен как копировать, так и сортировать векторы. В идеале, только сортировка должна быть частью эталона. Я попытался создать версию, которая использовала массив из 1000 несортированных векторов (поэтому мне не нужно было копировать несортированные данные в каждой итерации), но производительность была около то же самое или немного хуже, потому что это вызовет постоянные пропуски кэша, поэтому я вернулся к этой версии
и мой код:
#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;
}
нет причин, почему C++ всегда должен быть быстрее, чем C#. C# имеет несколько ключевых преимуществ, которые требуют большой осторожности, чтобы конкурировать с C++.
Основное, о чем я могу думать, это то, что динамические распределения смехотворно дешевы в .NET-land. Каждый раз, когда вектор C++, set или unordered_set (или любой другой контейнер) должен изменить размер или развернуть, он это очень дорого malloc
операции. В .NET распределение кучи немного больше, чем добавление смещения к указателю.
поэтому, если вы хотите, чтобы версия c++ конкурировала, вам, вероятно, придется решить это, позволяя вашим контейнерам изменять размер без необходимости выполнять фактические распределения кучи, возможно, используя пользовательские распределители для контейнеров (возможно, boost::pool может быть хорошей ставкой, или вы можете попробовать прокатить свой собственный)
другой вопрос, что set_difference
работает только на сортированный ввод, и для воспроизведения результатов тестов, которые включают сортировку, мы должны сделать новую копию несортированных данных в каждой итерации, что дорого (хотя опять же, использование пользовательских распределителей поможет много). Я не знаю, какую форму принимает ваш ввод, но возможно, что вы можете отсортировать свой ввод напрямую, не копируя его, а затем запустить set_difference
непосредственно на это. (Это было бы легко сделать, если ваш вход является массивом или контейнером STL, по крайней мере.)
один из ключей преимущества STL в том, что он настолько гибкий, что может работать практически на любой входной последовательности. В C# вам в значительной степени нужно скопировать входные данные в список или словарь или что-то еще, но в C++ вы можете уйти с запуском std::sort
и set_intersection
на сырье.
наконец, конечно, попробуйте запустить код через профилировщик и посмотреть, где именно тратится время. Вы также можете попробовать запустить код через GCC вместо этого. Это мое впечатление, что STL производительность в MSVC иногда немного странный. Возможно, стоит протестировать под другим компилятором, чтобы увидеть, есть ли у вас похожие тайминги.
наконец, вы можете найти эти сообщения в блоге, относящиеся к производительности C++ vs C#: http://blogs.msdn.com/ricom/archive/2005/05/10/416151.aspx
мораль это по сути то да, вы можете получить лучшую производительность в C++, но это удивительный объем работы.
одна проблема, которую я вижу сразу, заключается в том, что вы передаете наборы в C++ по значению, а не по ссылке const. Так вы копируете их каждый раз, когда передаете по кругу!
кроме того, я бы не использовал набор для цели set_intersection
. Я бы использовал что-то вроде
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();
}
этот код, однако, по-прежнему выделяется внутри функции. Еще быстрее было бы
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();
}
а затем выделите царапину перед запуском таймера.
хотя, если вы просто глядя на размер, написанный от руки для цикла, в сочетании с set:: find может дать еще лучшие результаты.
использовать это...
vector<int> set1(10000);
vector<int> set2(1000);
... получить векторы ненулевого начального размера. Затем не используйте push_back, а просто обновите значения напрямую.
Я бы изменил C++ "runIntersectionTest", чтобы принимать ссылки const на контейнеры, а не копировать их при каждом вызове. (Код C# будет использовать ссылки.)
Это также может быть стоит посмотреть на boost Бессвязный Набор контейнер, который специально оптимизирован для определенных видов большой набор операций.
Он работает, рассматривая группу множеств как объединения нескольких непересекающихся множеств, что позволяет создавать другие множества, такие как пересечения или объединения очень дешево, как только начальный набор непересекающихся множеств построен. Если вы ожидаете выполнения множества операций набора для наборов, которые не сильно меняются, вы можете наверное, это будет очень быстро. Если, с другой стороны, вы используете каждый набор один раз и выбрасываете его, он, вероятно, не будет делать слишком много.
в любом случае, вы бы сделали себе одолжение, по крайней мере, поэкспериментировать с этим, чтобы увидеть, дает ли это вам какой-либо удар в вашем конкретном случае.
кстати, если у вас большие отсортированные наборы std::set_intersection не самый быстрый алгоритм. std:: set_intersection занимает до 2*(m+n)-1 сравнения, но алгоритмы, подобные одному из Baeza-Yates, могут быть быстрее. Для малого m Baeza-Yates - O(m * log(n)), а для n = alpha * m-O (n). Основная идея заключается в том, 2 способ бинарные поиск.
http://citeseerx.ist.psu.edu/viewdoc/download?doi=10.1.1.91.7899&rep=rep1&type=pdf
экспериментальный анализ алгоритма быстрого пересечения для отсортированных последовательностей Рикардо Баэса-Йейтс и Алехандро Сэлинджер
или
Р. Баэса-Йейтс. Быстрый алгоритм пересечения множества для отсортированных последовательностей. В Материалы 15-го ежегодного симпозиума по комбинаторному сопоставлению шаблонов (CPM 2004), Springer LNCS 3109, pp 400-408, Стамбул, Турция, Июль 2004 Года.
Ниже приведено объяснение и реализация Эрика Фрея, где он показывает значительно более быстрые результаты, чем std::set_intersection с двоичным зондом. Я еще не пробовал его код.
http://fawx.com/
- выберите медианный элемент, A, в меньший набор.
- поиск элемента позиции вставки, B, in большой набор.
- если A и B равны, добавьте элемент результат.
- повторите шаги 1-4 на непустые подмножества по обе стороны от элементов A и 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
}
}
// щуп.ГЭС
/**
* двоичный зонд: выберите следующий элемент, выбрав точку на полпути между низким и высоким
*/
шаблон
структура binary_probe
{
RandomAccessIterator оператор()(RandomAccessIterator начать, RandomAccessIterator конца, с const Т & значение)
{
возврат begin + ((end-begin) > > 1);
}
};
/**
* lower_bound: как lower_bound stl, но с различными видами зондирования
* обратите внимание на внешний вид шаблона параметра rare template!
*/
шаблон
RandomAccessIterator lower_bound(начало RandomAccessIterator, конец RandomAccessIterator, const T & value)
{
RandomAccessIterator яму;
Зонд pfunc; / / зонд-функтор (хочет получить функцию вверх)
while (begin
/*
* на этот раз с компаратором!
*/
шаблон
RandomAccessIterator lower_bound(RandomAccessIterator начать, RandomAccessIterator конца, с const Т & значение, компаратор СМР)
{
RandomAccessIterator яму;
Зонд pfunc;
while (begin
поскольку вы используете Visual Studio вы должны проверить, есть ли у вас _SECURE_SCL
установите значение 1 (обычно, если вы явно не установили его, это будет 1). Если он установлен, весь STL-код будет проверен на диапазон, даже в сборках выпуска. Обычно код замедляется на 10-15%.
Кажется, Microsoft не знала, что, например, std::vector уже имеет интерфейс, если вы хотите проверить диапазон: std::vector::at()!
(извините, пришлось снять его с моего сундук.)
в любом случае основная неэффективность заключается в том, что вы копируете контейнеры, а не передаете их по значению. Используйте ссылки на (попробуйте) сравнить яблоки и яблоки вместо яблок и бананов.
Я знаю, что ваше решение работает нормально, но вы пробовали использовать реализации STL:
Он уже может быть оптимизирован для вашей plataform, поэтому я бы дал ему шанс
хорошо, после многих отзывов я обновил исходный вопрос несколько раз:
- тесты теперь каждый запуск 1,000 раз
- код C# теперь использует таймер с более высоким разрешением
- структуры данных теперь заполняются перед тестами
результатом этого до сих пор является то, что C# по-прежнему ~5x быстрее, чем c++.
спасибо всем за ваши идеи/предложения.
обновление:
Я изменил код set_intersection, чтобы использовать векторы и сортировать их (вместо использования отсортированного класса set), и теперь это намного быстрее:
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
имейте в виду: больший набор создается отсортированным, поэтому сортировка может не занять много времени в этом примере.
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;
}
вы все еще передаете векторы по значению. Что было бы нормально, если бы вы не копировали их.
inserter не помещал значения в конец вектора, где это быстро. Он сделал это только при первой вставке после этого он вставил значение в начале массива (где end используется для указания).
вы, где ищете значение дважды в версии хэш-карты, когда вы обновили значение. Почему это событие value обновлено?
запустите этот код и опубликуйте свои тайминги.
// 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;
}
последний тест:
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
Я думаю, что 504 - 495 различие происходит потому, что есть несколько значений Боян.
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;
}