如何优化在经常调用的函数中将大型std :: unordered_map临时用作复用?

时间:2019-01-24 06:08:54

标签: c++ memory unordered-map

带有工作示例的简化问题:我想多次重用std :: unordered_map(我们称它为umap),类似于下面的伪代码(它没有任何意义)。如何使此代码运行更快?

#include <iostream>
#include <unordered_map>
#include <time.h>

unsigned size = 1000000;

void foo(){
    std::unordered_map<int, double> umap;
    umap.reserve(size);
    for (int i = 0; i < size; i++) {
        // in my real program: umap gets filled with meaningful data here
        umap.emplace(i, i * 0.1);
    }
    // ... some code here which does something meaningful with umap
}

int main() {

    clock_t t = clock();

    for(int i = 0; i < 50; i++){
        foo();
    }

    t = clock() - t;
    printf ("%f s\n",((float)t)/CLOCKS_PER_SEC);

    return 0;
}

在我的原始代码中,我想将矩阵条目存储在umap中。在每次对foo的调用中,键值从0到N开始,并且在每次对foo的调用中N可以不同,但​​是索引的上限为10M。另外,值可以不同(与此处始终为i*0.1的伪代码相反)。

我试图将umap设为非局部变量,以避免在每次调用中重复umap.reserve()的内存分配。这要求在umap.clear()的末尾调用foo,但实际上这要比使用局部变量(我测量过)慢。

4 个答案:

答案 0 :(得分:3)

我认为没有什么好方法可以直接完成您要寻找的内容-也就是说,您必须先清除地图,才能清除地图。我想您可以预先分配许多地图,并且只将其中每个地图一次性用作“一次性地图”,然后在下一次通话期间继续使用下一张地图,但是我怀疑这会给您可以提高整体速度,因为最后必须全部清除所有速度,并且在任何情况下都将占用大量RAM并且对缓存不友好(在现代CPU中,RAM访问通常性能瓶颈,因此最大程度地减少高速缓存未命中数是提高效率的方法。

我的建议是,如果清晰速度至关重要,则可能需要完全放弃使用unordered_map,而应使用更简单的std::vector之类的东西-在这种情况下,您可以只需在向量中保留一个有效项的整数,然后“清除”向量就是将计数设置回零即可。 (当然,这意味着您牺牲了unordered_map的快速查找属性,但是也许在此计算阶段不需要它们?)

答案 1 :(得分:2)

一种简单有效的方法是按如下所示通过引用重复使用相同的容器和内存。 通过这种方法,可以避免它们的递归内存分配std::unordered_map::reservestd::unordered_map::~unordered_map都具有复杂度O(元素数量):

void foo(std::unordered_map<int, double>& umap)
{        
    std::size_t N = ...// set N here

    for (int i = 0; i < N; ++i)
    {
        // overwrite umap[0], ..., umap[N-1]
        // If umap does not have key=i, then it is inserted.
        umap[i] = i*0.1;
    }

    // do something and not access to umap[N], ..., umap[size-1] !
}

主叫方如下:

std::unordered_map<int,double> umap;
umap.reserve(size);

for(int i=0; i<50; ++i){
    foo(umap);
}

但是由于您的键集始终是连续的整数{1,2,...,N},因此我认为,std::vector可以避免散列计算,因此更适合保存值umap[0], ..., umap[N]

void foo(std::vector<double>& vec)
{    
    int N = ...// set N here

    for(int i = 0; i<N; ++i)
    {
        // overwrite vec[0], ..., vec[N-1]
        vec[i] = i*0.1;
    }

    // do something and not access to vec[N], ..., vec[size-1] !            
}

答案 2 :(得分:1)

您是否尝试过通过使用简单数组来避免所有内存分配?上面您已经说过,您知道所有对sudo zypper install unixODBC-devel 的呼叫中umap的最大大小:

foo()

答案 3 :(得分:0)

正如我在评论中所建议的,对于您的用例而言,封闭式哈希将更好。这是一个快速且脏的封闭哈希表,具有固定的哈希表大小,您可以尝试使用:

template<class Key, class T, size_t size = 1000003, class Hash = std::hash<Key>>
class closed_hash_map {
    typedef std::pair<const Key, T>                     value_type;
    typedef typename std::vector<value_type>::iterator  iterator;
    std::array<int, size>                               hashtable;
    std::vector<value_type>                             data;
 public:
    iterator begin() { return data.begin(); }
    iterator end() { return data.end(); }
    iterator find(const Key &k) {
        size_t h = Hash()(k) % size;
        while (hashtable[h]) {
            if (data[hashtable[h]-1].first == k)
                return data.begin() + (hashtable[h] - 1);
            if (++h == size) h = 0; }
        return data.end(); }
    std::pair<iterator, bool> insert(const value_type& obj) {
        size_t h = Hash()(obj.first) % size;
        while (hashtable[h]) {
            if (data[hashtable[h]-1].first == obj.first)
                return std::make_pair(data.begin() + (hashtable[h] - 1), false);
            if (++h == size) h = 0; }
        data.emplace_back(obj);
        hashtable[h] = data.size();
        return std::make_pair(data.end() - 1, true); }
    void clear() {
        data.clear();
        hashtable.fill(0); }
};

通过在需要时根据需要动态调整哈希表的大小,可以使其更加灵活,而使用robin-hood替换可以使其更加高效。