繁体   English   中英

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

[英]How to optimize reusing a large std::unordered_map as a temporary in a frequently called function?

使用工作示例的简化问题:我想多次重用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()内存分配。 这需要在foo的末尾调用umap.clear() ,但结果实际上比使用局部变量(我测量它)慢。

我不认为有任何好方法可以直接完成你想要的东西 - 即你不能在不清除地图的情况下清除地图。 我想你可以预先分配一些地图,只需将它们中的每一个用作“一次性地图”,然后在下次通话时继续使用下一张地图,但我怀疑这会给你有任何整体加速,因为在它结束时你必须立即清除所有这些,并且无论如何它将是非常密集的RAM和缓存不友好(在现代CPU中,RAM访问通常是性能瓶颈,因此最大限度地减少缓存未命中数量是最大化效率的方法)。

我的建议是,如果clear-speed如此重要,你可能需要完全不使用unordered_map ,而是使用更简单的东西,比如std::vector - 在这种情况下,你可以简单地保持一个有效数字-items-in-the-vector整数,“清除”向量只是将计数设置回零。 (当然,这意味着你牺牲了unordered_map的快速查找属性,但也许你在计算的这个阶段不需要它们?)

一种简单有效的方法是一次又一次地重复使用相同的容器和内存,并按如下方式传递引用。 在这个方法中,你可以避免它们的递归内存分配std::unordered_map::reservestd::unordered_map::~unordered_map都具有复杂度O(elemen of nummenrs):

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] !            
}

您是否尝试使用简单数组来避免所有内存分配? 您已经在上面说过,您知道所有对foo()调用的最大umap大小:

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

constexpr int size = 1000000;
double af[size];

void foo(int N) {
    // assert(N<=size);
    for (int i = 0; i < N; i++) {
        af[i] = i;
    }
    // ... af
}

int main() {    
    clock_t t = clock();

    for(int i = 0; i < 50; i++){
        foo(size /* or some other N<=size */);
    }

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

    return 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替换更有效。

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM