[英]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::reserve
和std::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.