我有大量的数据被读入内存 - 暂时,但对于系统来说是必需的。
我一直在检查std::vector
以及std::unordered_map
的效果
对于std::vector
,我使用了struct
类型:
struct information{
std::string name;
unsigned int offset;
}
对于std::unordered_map
,我使用std::string
作为密钥,unsigned int offset
作为值。
如果,让我们说,其中2 000 000被加载到内存中,我尝试了以下内容并得到了这些结果:
std::vector:
在随机字符串上,如果在向量上调用了保留字,则永远不会超过32个字符。
std::vector<information> vec;
vec.reserve(2500000);
插入
vec.push_back({dataName, offset});
非常快。但是,尝试查找数据非常缓慢。发现是这样实现的:
auto it = std::find_if(vec.begin(), vec.end(), [&name](information &info) -> bool {return info.name == name); });
有意义的是,它是一个大型向量,并且在名称比较中找到了正确的struct
。但是表现极差。使用的内存很好 - 我假设内存增长的一部分是由于std::string
大小调整大小。
关于矢量实现的问题是:有没有办法增加查找时间?我知道可以对矢量进行排序以增加查找时间,但是在排序矢量时会浪费时间。特别是在这种尺寸的矢量上。
std::unordered_map
插入
std::unordered_map<std::string, unsigned int> unordMap;
unordMap.reserve(2500000);
unordMap.emplace(name, offset);
需要很长时间。在预先留出空间以试图缩短插入时间时,会发生以下情况:
当不调用reserve时,插入结束时的内存更多,没有保留内存仍然比矢量实现更多。储备并没有真正改善插入时间。
当然,查询速度非常快。我关于std::unordered_map
的问题是可以改善插入时间和内存使用情况吗?
如果这些都不能完成,那么我的下一个问题可能会非常明显。有没有办法在这两个数据结构之间得到结果?大量数据的最佳效果是什么?
答案 0 :(得分:4)
struct information{
std::string name;
unsigned int offset;
information(information const&)=default;
information(information&&)=default;
information(std::string n, unsigned o):name(std::move(n)),offset(o),hash(std::hash<std::string>()(name)) {};
information():information("",0) {};
bool operator<( information const& o ) const {
return tie() < o.tie();
}
std::tuple<std::size_t, std::string const&> tie() const { return std::tie(hash, name); }
private:
std::size_t hash;
};
对std::vector
使用上述结构。
添加完所有数据后,std::sort
。
要找到与name
匹配的内容,请执行以下操作:
struct information_searcher {
struct helper {
std::tuple<std::size_t, std::string const&> data;
helper( std::string const& o ):data(std::hash<std::string>()(o), o) {};
helper( helper const& o ) = default;
helper( information const& o ):data(o.tie()) {}
bool operator<( helper const& o ) const { return data < o.data; }
};
bool operator()( helper lhs, helper rhs ) const { return lhs < rhs; }
};
information* get_info_by_name( std::string const& name ) {
auto range = std::equal_range( vec.begin(), vec.end(), information_searcher::helper(name), information_searcher{} );
if (range.first == range.second) {
return nullptr;
} else {
return &*range.first;
}
}
这是一个几乎零开销的查找。
我们在这里做的是对字符串进行哈希处理(以便进行快速比较),如果我们发生冲突,请回到std::string
比较。
information_searcher
是一个允许我们搜索数据的类,而不必创建information
(这需要浪费的分配)。
get_info_by_name
返回一个指针 - nullptr
如果找不到,指针指向第一个带有该名称的元素。
更改information.name
是不礼貌的,并使hash
字段不正确。
这可能会使用比原始std::vector
版本更多的内存。
一般情况下,如果您的作品包含“将一堆内容添加到表格中”。然后做一堆查找&#39;,最好的办法是建立一个std::vector
,快速排序,然后使用equal_range
进行查找。 map
和unordered_map
针对大量混合插入/删除/等进行了优化。
答案 1 :(得分:1)
大向量的问题是当您不知道所需对象的索引时的查找时间。正如你所说的,改进它的一种方法是保持有序向量并对其进行二进制搜索。这样,查找时间将不是线性复杂性,而是对数复杂度,这对于非常大的容器节省了相当多的时间。这是std::map
中使用的查找(有序的查找)。您可以使用std::vector
上的std::lower_bound
或std::equal_range
进行类似的二进制搜索。
大型无序映射的问题完全不同:这种容器使用散列函数和模数计算,以便根据元素在标准数组中放置元素。因此,当std::unordered_map
中有n个元素时,您不太可能只需要一个n元素长数组,因为某些索引不会被填充。您将至少使用hash-and-modulo生成的最大索引。提高内存使用率和插入时间的一种方法是编写自己的哈希函数。但根据您使用的字符串类型,可能会很难。
答案 2 :(得分:1)
vector通常实现为“动态数组”,应该是最节省内存的。 有了良好的预约策略,它可以插入O(1)=快。搜索是O(n)=非常糟糕。
你可以通过排序来帮助矢量(如果你第一次加载然后搜索比我认为最好 - std :: sort + std :: binary_search)。
您也可以使用std :: lower_bound实现insert-sort之类的操作。 insert = O(log n)= good,search = O(log n)= good
map(ordered)实际上可以做同样的工作,但也可以使用tree =较少的内存效率,访问和排序的向量一样好(但可能更少的重新分配,但在你的情况下,排序的向量仍然是最好的)
unordered_map通常使用哈希表来实现=一些内存开销但快速操作(插入不能像未排序的向量那样快,但应该仍然非常快)。散列的问题在于它可以快速甚至最快,但也可能是最差的解决方案(在极端条件下)。上面的结构(排序的矢量和地图/树是稳定的,总是表现相同 - 对数复杂度)。
答案 3 :(得分:0)
嗯,这里的最佳解决方案是创建std :: map,它在插入和查找方面都具有对数的复杂性。虽然,我没有看到你为什么不使用std :: vector的原因。使用快速排序甚至可以对2M元素进行排序时速度非常快,特别是如果你只进行一次。 std :: binary_search非常快。如果您需要在插入之间进行大量查找,请仔细考虑。