std :: map和performance,相交集

时间:2009-06-29 01:39:14

标签: c++ stl map intersection

我正在交叉一些数字,并通过存储每次在地图中看到数字时的计数来执行此操作。

我发现表现很慢。

详细说明: - 其中一套有150,000个号码 - 该组与另一组的交集第一次约需300ms,第二次约为5000ms - 我还没有做过任何分析,但是每次我在malloc.c中进行交集时都会破坏调试器!

那么,我该如何改善这种表现呢?切换到不同的数据结构?有些如何提高map的内存分配性能?

更新

  1. 有没有办法问std :: map或者 boost :: unordered_map预分配 一些空间?
  2. 或者,有没有提示有效使用这些?
  3. UPDATE2:

    请参阅Fast C++ container like the C# HashSet<T> and Dictionary<K,V>?

    Update3:

    我对set_intersection进行了基准测试并得到了可怕的结果:

    (set_intersection) Found 313 values in the intersection, in 11345ms
    (set_intersection) Found 309 values in the intersection, in 12332ms
    

    代码:

    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(); 
    }
    

9 个答案:

答案 0 :(得分:2)

你绝对应该使用速度更快的预分配矢量。与stl集进行集合交集的问题在于,每次移动到下一个元素时,您都在追逐动态分配的指针,这很容易不会出现在CPU缓存中。使用向量时,下一个元素通常位于缓存中,因为它在物理上接近前一个元素。

使用向量的技巧是,如果你不为这样的任务预先分配内存,它将执行EVEN WORSE,因为它会在初始化步骤中重新调整内存时继续重新分配内存。

尝试类似这样的事情 - 它会更快。

int runIntersectionTestAlgo() { 

vector<char> vector1; vector1.reserve(100000);
vector<char> vector2; vector2.reserve(1000);

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

sort(vector1.begin(), vector1.end());

// 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);
}

sort(vector2.begin(), vector2.end());

// Reserve at most 1,000 spots for the intersection
vector<char> intersection; intersection.reserve(min(vector1.size(),vector2.size()));
set_intersection(vector1.begin(), vector1.end(),vector2.begin(), vector2.end(),back_inserter(intersection));

return intersection.size(); 
}

答案 1 :(得分:1)

在不了解您的问题的情况下,“与优秀的探查者一起检查”是我能给出的最好的一般建议。除此之外......

如果内存分配是您的问题,请切换到某种池化分配器,以减少对malloc的调用。 Boost有许多自定义分配器,应与std::allocator<T>兼容。事实上,如果你已经注意到调试中断样本总是以malloc结尾,你甚至可以在分析之前尝试这个。

如果您的数字空间已知密集,您可以使用数字作为向量中的索引切换到使用基于vectorbitset的实现。

如果您的数字空间大部分稀疏但有一些自然聚类(这是一个很大的 if ),您可以切换到矢量图。使用高阶位进行映射索引,使用低阶位进行向量索引。这在功能上非常类似于简单地使用池化分配器,但它可能会为您提供更好的缓存行为。这是有道理的,因为您向计算机提供了更多信息(集群是显式的,缓存友好的,而不是您期望从池分配的随机分布)。

答案 2 :(得分:1)

我会根据这个建议对它们进行排序。已有STL集算法在排序范围内运行(如set_intersection,set_union等):

set_intersection

答案 3 :(得分:1)

我不明白为什么你必须使用地图来做交叉。就像人们所说的那样,你可以将这些集合放在std::set中,然后使用std::set_intersection()

或者你可以将它们放入hash_set。但是你必须手动实现交集:从技术上讲,你只需要将其中一个集合放入hash_set,然后循环遍历另一个集合,并测试hash_set中是否包含每个元素

答案 4 :(得分:0)

你的交叉算法是什么?也许有一些改进?

这是另一种方法

我不知道它更快或更慢,但它可能是尝试的东西。在此之前,我还建议使用分析器来确保您真正在使用热点。更改相交的数字组以改为使用std::set<int>。然后迭代查看您找到的每个值的最小值。对于最小集合中的每个值,使用find方法查看每个其他集合中是否存在该数字(对于性能,从最小到最大搜索)。

如果在所有集合中找不到数字,则会对此进行优化,因此如果交点相对较小,则可能很快。

然后,将交叉点存储在std::vector<int>中 - 使用push_back插入也非常快。

这是另一种替代方法

将数字集更改为std::vector<int>并使用std::sort从最小到最大排序。 然后使用std::binary_search查找值,使用与上面大致相同的方法。这可能比搜索std::set更快,因为数组在内存中的包装更紧密。实际上,没关系,你可以在锁步中迭代值,看看那些相同的价值。仅增加小于上一步中看到的最小值的迭代器(如果值不同)。

答案 5 :(得分:0)

可能是你的算法。根据我的理解,你正在旋转每一组(我希望它是一个标准组),并将它们扔进另一个地图。这样做了很多你不需要做的工作,因为标准集的键已按排序顺序排列。相反,采取类似“合并排序”的方法。旋转每个iter,解除引用以找到min。计算具有该最小值的数字,并递增它们。如果计数为N,则将其添加到交叉点。重复,直到第一张贴图结束(如果你在开始之前比较尺寸,你不必每次都检查每张贴图的结束)。

响应更新:确实存在通过预留空间来加速内存分配的能力,例如boost::pool_alloc。类似的东西:

std::map<int, int, std::less<int>, boost::pool_allocator< std::pair<int const, int> > > m;

但老实说,malloc非常擅长它的功能;在做任何过于极端的事情之前我都会介绍一下。

答案 6 :(得分:0)

与地图的交点很慢,请尝试hash_map。 (但是,并非所有STL实现都提供此功能。

或者,对两个地图进行排序并以类似合并排序的方式进行排序。

答案 7 :(得分:0)

查看您的算法,然后选择正确的数据类型。如果您要进行类似集合的行为,并希望进行交集等,std::set是要使用的容器。

由于它的元素以排序的方式存储,插入可能会花费你O(log N),但是与另一个(排序!)std::set的交集可以在线性时间内完成。

答案 8 :(得分:0)

我想出了一些问题:如果我将调试器连接到RELEASE或DEBUG版本(例如在IDE中点击F5),那么我会遇到可怕的时间。